-
-
[翻译]野蛮fuzz - part 6:持久性fuzz
-
发表于: 2024-9-3 09:30 3248
-
距离我上次写这类文章已经有一段时间了,今年我的目标之一是写更多的文章,所以我们再次回到了这里。我正在进行的一个副项目正逐步接近一个好的停顿点,因此我将有更多的闲暇时间来做自己的研究并再次写博客。期待今年分享更多内容。
在初学者模糊测试圈子中(显然我也是其中一员),最常见的问题之一是如何HOOK目标,使其能够在内存中进行模糊测试,有些人称之为“持久性”模糊测试,以获得更高的性能。持久性模糊测试有一个特定的用例,即当目标在不同的模糊测试用例之间不涉及太多全局状态时,这种方式会很有用。一个例子是对库中的单个API或二进制文件中的单个函数进行紧密的模糊测试循环。
这种模糊测试风格比一次又一次从头开始重新执行目标要快,因为我们绕过了与创建和销毁任务结构相关的所有繁重的系统调用/内核例程。
然而,对于没有源代码的二进制目标来说,在不进行深入逆向工程的情况下(真恶心,工作?呃),有时很难辨别在执行任何代码路径时我们影响了哪些全局状态。此外,我们通常希望模糊测试更广泛的循环。模糊测试一个返回一个结构体的函数并没有多大帮助,因为该结构体在我们的模糊测试工作流程中从未被读取或使用。考虑到这些情况,我们通常发现“快照”模糊测试对于二进制目标,甚至是那些我们有源代码但经过企业构建系统处理的生产二进制文件来说,是一种更稳健的工作流程。
因此,今天我们将学习如何将一个任意的仅二进制目标(该目标从用户那里接收输入文件)转换为一个从内存中接收输入的目标,并使其能够在不同的模糊测试用例之间重置状态。
在这篇博客文章中,我们将使用 objdump
作为快照模糊测试的HOOK目标。这能够满足我们的需求,因为它相对简单(单线程、单进程),并且它是一个常见的模糊测试目标,尤其是在人们开发模糊测试工具时。本文的重点不是通过沙盒化像Chrome这样复杂的目标来打动你,而是向初学者展示如何开始思考HOOK目标。你需要将你的目标改造得面目全非,但保持相同的语义。你可以尽情发挥创造力,坦白说,有时候HOOK目标是与模糊测试相关的最令人满意的工作之一。成功地将目标沙盒化,并使其与模糊测试器良好配合,感觉非常棒。那么我们开始吧。
第一步是确定我们想如何改变 objdump
的行为。让我们尝试在 strace
下运行它,并反汇编 ls
,看看它在系统调用级别上如何表现,命令为 strace objdump -D /bin/ls
。我们要寻找的是 objdump
开始与我们的输入(在本例中为 /bin/ls
)交互的地方。在输出中,如果你滚动浏览过那些样板内容,你可以看到 /bin/ls
的首次出现:
请记住,在阅读本文时,如果你在家里跟随操作,你的输出可能不会与我的完全匹配。我可能运行的是与你不同的发行版以及不同版本的 objdump
。但这篇博客文章的目的是展示一些概念,你可以自行发挥创意。
我还注意到,程序在执行结束前并不会关闭我们的输入文件:
这点很重要,我们需要让我们的HOOK程序能很好地模拟一个输入文件,因为 objdump
并不会一次性将文件读入内存缓冲区或使用 mmap()
映射输入文件。它会在整个 strace
输出过程中不断地从文件中读取数据。
由于我们没有目标的源代码,我们将通过使用 LD_PRELOAD
共享对象来影响其行为。通过使用 LD_PRELOAD
共享对象,我们应该能够HOOK与输入文件交互的系统调用的包装函数,并修改它们的行为以满足我们的需求。如果你不熟悉动态链接或 LD_PRELOAD
,现在是一个很好的时机去搜索更多信息,这是一个很好的起点。首先,我们先加载一个“Hello, World!”的共享对象。
我们可以利用 gcc
的函数属性(Function Attributes)来在共享对象被目标加载时执行代码,通过构造器属性(constructor attribute)来实现。
因此,迄今为止我们的代码将如下所示:
我将编译所需的标志添加到了文件顶部作为注释。这些标志来自于我之前阅读的关于使用 LD_PRELOAD
共享对象的博客文章:https://tbrindus.ca/correct-ld-preload-hooking-libc/。
现在我们可以使用 LD_PRELOAD
环境变量,并运行带有我们的共享对象的 objdump
,它应该在加载时打印信息:
它有效,现在我们可以开始寻找需要HOOK的函数了。
首先我们需要做的是创建一个虚假的文件名给 objdump
,以便我们可以开始测试。我们将 ls
可执行文件复制到当前工作目录,并将其命名为 fuzzme
。这将允许我们在测试时通用地操作HOOK程序。现在我们有了 strace
输出,我们知道 objdump
在调用 openat()
之前,会多次对输入文件路径(/bin/ls)调用 stat()
。由于我们知道文件尚未被打开,并且系统调用的第一个参数使用路径,我们可以猜测这个系统调用是由 libc
导出的 stat()
或 lstat()
包装函数引发的。我假设是 stat()
,因为我们在我的机器上没有处理 /bin/ls
的符号链接。我们可以添加一个 stat()
的HOOK来测试是否命中,并检查它是否被调用来处理我们的目标输入文件(现在改为 fuzzme
)。
为了创建一个HOOK,我们将遵循一个模式,即通过 typedef
定义指向真实函数的指针,然后将指针初始化为 NULL
。一旦我们需要解析我们所HOOK的真实函数的位置,我们可以使用 dlsym(RLTD_NEXT, <symbol name>)
来获取它的位置,并将指针值更改为真实符号地址。(稍后这将变得更清晰)。
现在我们需要HOOK stat()
,它在 man 3
中作为一个条目(意味着它是一个 libc
导出的函数),同时也在 man 2
中作为一个条目(意味着它是一个系统调用)。这让我困惑了很长时间,因为这个命名冲突,我经常误解系统调用的实际工作原理。你可以阅读我最早的研究博客文章之一,在那里这种困惑显而易见,我也经常做出错误的断言。(顺便说一下,我永远不会编辑那些有错误的旧博客文章,它们就像时间胶囊,对我来说这很酷)。
我们想要编写一个函数,当被调用时,只需打印一些信息并退出,以便我们知道我们的HOOK被命中了。目前,我们的代码如下所示:
然而,如果我们编译并运行该代码,我们并没有打印任何内容并退出,所以我们的HOOK没有被调用。出了点问题。有时候,libc
中与文件相关的函数有 64 位变体,比如 open()
和 open64()
,它们会根据配置和标志的不同而交替使用。我尝试HOOK stat64()
,但仍然没有成功。
幸运的是,我并不是第一个遇到这个问题的人。Stackoverflow 上有一个很棒的答案,讨论了这个问题,解释了 libc
并没有像其他函数(如 open()
和 open64()
)那样导出 stat()
,而是导出了一个叫做 __xstat()
的符号。这个符号具有略微不同的签名,并需要一个名为 version
的新参数,用于描述调用者期望的 stat
结构体的版本。这一切本应在底层自动处理,但我们现在必须自己让这些“魔法”发挥作用。对于 lstat()
和 fstat()
也是同样的规则,它们分别有 __lxstat()
和 __fxstat()
。
我在这里找到了这些函数的定义。于是我们可以将 __xstat()
HOOK添加到我们的共享对象中,替换掉 stat()
,看看是否有不同的结果。现在我们的代码如下所示:
现在,如果我们运行我们的共享对象,我们得到了预期的结果,某处我们的HOOK被命中。现在我们可以帮自己一把,打印出HOOK请求的文件名,然后实际上代表调用者调用真实的 __xstat()
。当我们的HOOK被命中时,我们需要通过名称解析真正的 __xstat()
的位置,所以我们会向共享对象中添加一个符号解析函数。现在我们的共享对象代码如下所示:
好了,现在当我们运行这个代码,并检查我们的打印语句时,事情变得有趣起来了。
现在我们可以开始享受过程了。
这个HOOK的目的是欺骗 objdump
,让它以为成功地对输入文件执行了 stat()
。记住,我们正在制作一个快照模糊测试HOOK,所以我们的目标是不断地创建新输入并通过这个HOOK传递给 objdump
。最重要的是,我们的HOOK需要能够将我们存储在内存中的可变长度输入表示为文件。每个模糊测试用例中,文件长度可能会发生变化,而我们的HOOK需要适应这一点。
此时我的想法是创建一个看起来“合法”的 stat
结构体,它通常会为我们的实际文件 fuzzme
(只是 /bin/ls
的副本)返回。我们可以全局存储这个 stat
结构体,并在每个新的模糊测试用例中只更新大小字段。因此,我们的快照模糊测试工作流程的时间线可能如下所示:
因此,我们可以想象模糊测试器有类似这样的伪代码例程,尽管它可能是跨进程的,并且需要 process_vm_writev
。
一个重要的事项是,如果快照模糊测试器在每次模糊测试迭代时都将 objdump
恢复到其快照状态,我们必须小心不要依赖任何全局的可变内存。全局的 stat
结构体是安全的,因为它将在构造函数期间实例化,然而,模糊测试器的快照恢复例程将在每次模糊测试迭代时将其 size
字段恢复到原始值。
我们还需要一个全局的、可识别的地址来存储可变的全局数据,比如当前输入的大小。几个快照模糊测试器具有忽略连续内存范围以用于恢复目的的灵活性。因此,如果我们能够在可识别的地址上创建一些连续的内存缓冲区,我们可以让我们的假想模糊测试器在快照恢复时忽略这些内存范围。因此,我们需要有一个地方来存储输入,以及有关其大小的信息。然后我们可以以某种方式告诉模糊测试器这些位置,当它生成新输入时,它会将其复制到输入位置,然后更新当前输入大小信息。
所以现在我们的构造函数有了一个额外的任务:设置输入位置以及输入大小信息。我们可以通过调用 mmap()
来轻松实现这一点,这使我们能够通过 MAP_FIXED
标志指定我们希望将映射映射到的地址。我们还会创建一个 MAX_INPUT_SZ
定义,以便我们知道从输入位置映射多少内存。
仅仅是与映射输入本身及其大小信息相关的函数看起来是这样的。请注意,我们使用了 MAP_FIXED
并检查了 mmap()
的返回地址,以确保调用成功但没有将我们的内存映射到不同的位置:
mmap()
实际上会映射系统上页面大小的倍数(通常是 4096 字节)。所以,当我们要求映射 sizeof(size_t)
字节时,mmap()
就像:“嗯,这只是一页,兄弟”,并且从 0x1336000
到 0x1337000
给了我们一整页(不包括高端)。
随机附带提一下,在定义和宏中进行算术运算时要小心,就像我在这里用 MAX_INPUT_SIZE
所做的那样,预处理器很容易将你的文本替换为定义关键字,从而破坏一些操作顺序,甚至溢出特定的基本类型,如 int
。
现在我们已经为模糊测试器设置了存储输入及其大小信息的内存,我们可以创建那个全局的 stat
结构体了。但是我们实际上遇到了一个大问题。如果我们已经HOOK了 __xstat()
,我们如何调用 __xstat()
以获取我们的“合法” stat
结构体呢?我们会命中自己的HOOK。为了解决这个问题,我们可以使用一个特殊的 __ver
参数调用 __xstat()
,我们知道这意味着它是从构造函数中调用的,变量是一个 int
类型,所以我们可以使用 0x1337
作为特殊值。这样,在我们的HOOK中,如果我们检查 __ver
并且它是 0x1337
,我们就知道它是从构造函数中调用的,我们可以实际对我们的真实文件进行 stat
调用并创建一个全局的“合法” stat
结构体。当我转储 objdump
对 __xstat()
的正常调用时,__version
总是值 1
,所以我们会在HOOK中将其修补回去。现在我们整个共享对象的源文件应该如下所示:
现在,如果我们运行这个代码,我们得到如下输出:
这很酷,这意味着 objdump
的开发人员做了一些正确的事情,他们的 stat()
会说:“嘿,这个文件长度为零,发生了什么怪事。”然后他们会输出这个错误消息并退出。干得好,开发者们!
所以我们已经确定了一个问题,我们需要模拟模糊测试器将一个真实的输入放入内存中,为此,我将开始使用 #ifdef
来定义我们是否在测试我们的共享对象。所以基本上,如果我们编译共享对象并定义 TEST
,我们的共享对象将复制一个“输入”到内存中,以模拟模糊测试器在模糊测试期间的行为,我们可以看看我们的HOOK是否工作得当。所以如果我们定义了 TEST
,我们将把 /bin/ed
复制到内存中,并更新我们的全局“合法” stat
结构体的大小成员,并将 /bin/ed
的字节放入内存中。
你现在可以按照如下方式编译共享对象以进行测试:
我们还需要设置我们的全局“合法” stat
结构体,代码应如下所示。记住,我们传递一个假的 __ver
变量让 __xstat()
HOOK知道是在构造函数例程中调用的,从而允许HOOK正常运行并为我们提供所需的 stat
结构体:
总的来说,我们的整个HOOK现在看起来是这样的:
现在,如果我们在 strace
下运行这个程序,我们注意到我们的两个 stat()
调用明显不见了。
我们不再看到 openat()
之前的 stat()
调用,并且程序没有在任何显著的地方崩溃。因此,这个HOOK似乎工作得很好。我们现在需要处理 openat()
,并确保我们实际上不与输入文件交互,而是欺骗 objdump
与内存中的输入交互。
我的非专家直觉告诉我,可能有几种方式使得 libc
函数在底层调用 openat()
。这些方式可能包括包装函数 open()
以及 fopen()
。我们还需要注意它们的 64 位变体(open64()
、fopen64()
)。我决定首先尝试HOOK fopen()
:
如果我们编译并运行我们的探索性HOOK,我们得到以下输出:
Bingo,成功找到目标。
所以现在我们可以稍微完善一下这个HOOK函数,使其按我们的预期运行。
fopen64()
的定义是:FILE *fopen(const char *restrict pathname, const char *restrict mode);
。返回的 FILE *
对我们来说有点麻烦,因为这是一个不透明的数据结构,调用者不应理解它的内容。也就是说,调用者不应访问这个数据结构的任何成员,也不应担心它的布局。你只需要将返回的 FILE *
作为一个对象传递给其他函数,例如 fclose()
。系统在这些相关函数中处理数据结构,这样程序员就不必担心特定的实现。
我们实际上并不知道返回的 FILE *
将如何被使用,它可能根本不会被使用,或者可能会被传递给诸如 fread()
之类的函数,因此我们需要一种方式返回一个令人信服的 FILE *
数据结构给调用者,这个数据结构实际上是从我们内存中的输入构建的,而不是从输入文件构建的。幸运的是,有一个叫做 fmemopen()
的 libc
函数,其行为与 fopen()
非常相似,也返回一个 FILE *
。所以我们可以继续创建一个 FILE *
返回给 fopen64()
的调用者,目标输入文件是 fuzzme
。感谢 @domenuk 向我展示了 fmemopen()
,我以前从未遇到过它。
不过有一个关键区别。fopen()
实际上会为底层文件获取文件描述符,而 fmemopen()
因为它实际上并未打开文件,所以不会。因此,在 FILE *
数据结构中的某处,如果它是从 fopen()
返回的,则存在一个底层文件的文件描述符,而如果它是从 fmemopen()
返回的,则不存在。这一点非常重要,因为诸如 int fileno(FILE *stream)
之类的函数可以解析一个 FILE *
并将其底层文件描述符返回给调用者。objdump
可能出于某种原因想要这样做,我们需要能够稳健地处理这个问题。因此,我们需要一种方法来知道有人是否试图使用我们伪造的 FILE *
的底层文件描述符。
我的想法是简单地找到 fmemopen()
返回的 FILE *
中包含文件描述符的结构成员,并将其更改为类似 1337 这样的荒谬值,这样如果 objdump
试图使用该文件描述符,我们就会知道它的来源,并可以尝试HOOK与该文件描述符的任何交互。现在我们的 fopen64()
HOOK应该看起来如下所示:
你可以看到我们:
你可能还注意到了一些安全检查,以确保事情不会被忽视。我们有一个全局变量 FILE *faked_fp
,我们将其初始化为 NULL
,这让我们知道是否多次打开了我们的输入(在后续尝试打开时它将不再是 NULL
)。
我们还检查了 mode
参数,以确保我们得到的是一个只读的 FILE *
。我们不希望 objdump
修改我们的输入或以任何方式写入它,如果它试图这样做,我们需要知道。
此时运行我们的共享对象会产生以下输出:
我的直觉告诉我,有什么东西试图与文件描述符 1337 进行交互。让我们再次在 strace
下运行,看看会发生什么。
在输出中,我们可以看到一些系统调用 fcntl()
和 fstat()
都是用文件描述符 1337 调用的,该描述符显然不存在于我们的 objdump
进程中,因此我们已经能够找到问题所在。
正如我们已经了解到的,libc
中没有直接导出的 fstat()
,它像 stat()
一样是那种奇怪的函数,我们实际上必须HOOK __fxstat()
。所以让我们尝试HOOK它,看看它是否会被调用用于我们的 1337 文件描述符。HOOK函数的初始代码如下:
现在我们还需要处理 fcntl()
,幸运的是,这个HOOK比较简单。如果有人请求 F_GETFD
(即与那个特殊的 1337 文件描述符关联的标志),我们只需返回 O_RDONLY
,因为它是以这些标志“打开”的,如果有人为不同的文件描述符调用它,我们暂时只会触发一个 panic。这个HOOK如下所示:
现在在 strace
下运行时,fcntl()
调用如预期那样消失了:
现在我们可以完善我们的 __fxstat()
HOOK逻辑。调用者希望通过传递特殊的文件描述符 1337 从函数中获取一个针对模糊测试目标 fuzzme
的 stat
结构体。幸运的是,我们有一个全局的 stat
结构体,在我们更新其大小以匹配内存中当前输入的大小(由我们和模糊测试器通过 INPUT_SIZE_ADDR
处的值进行跟踪)之后,我们可以返回这个结构体。因此,如果被调用,我们只需更新 stat
结构体的大小,并将我们的结构体通过 memcpy
复制到调用者的 *__stat_buf
中。我们完整的HOOK代码现在如下:
现在,如果我们运行这个代码,程序实际上不会崩溃,并且 objdump
可以在 strace
下干净地退出。
为了测试我们是否做得不错,我们将输出 objdump -D fuzzme
到一个文件中,然后我们将加载我们的HOOK共享对象并输出相同的命令。最后,我们将运行 objdump -D /bin/ed
并输出到一个文件中,以查看我们的HOOK是否生成了相同的输出。
然后我们对这些文件进行 sha1sum
:
我们实际上得到了三个不同的哈希值,我们希望HOOK和 /bin/ed
输出相同的结果,因为 /bin/ed
是我们加载到内存中的输入。
啊,它们的长度至少是一样的,这意味着一定存在一些细微的差异,diff
命令显示了哈希值不同的原因:
argv[]
数组中的文件名不同,这是唯一的区别。最终,我们能够向 objdump
提供一个输入文件,但实际上它从我们HOOK中的内存缓冲区获取输入。
还有一件事情,我们实际上忘记了 objdump
关闭了我们的文件,不是吗!于是我添加了一个快速的 fclose()
HOOK。如果 fclose()
只是想释放与 fmemopen()
返回的 FILE *
关联的堆内存,我们不会有任何问题;然而,它可能还会尝试调用 close()
关闭那个奇怪的文件描述符,而我们不希望这样做。最终这可能都无关紧要,但为了安全起见还是加上了。读者可以自行实验,看看会产生什么变化。假想的模糊测试器应该会在其快照恢复例程中恢复 FILE *
的堆内存。
有无数种方法可以实现这个目标,我只是想带你们走一遍我的思考过程。实际上,有很多很酷的事情你可以用这个HOOK做,其中一件事是HOOK malloc()
,让它在大规模分配时失败,这样我就不会浪费模糊测试周期在最终会超时的事情上。你还可以创建一个 at_exit()
卡点,这样无论如何,程序每次退出时都会执行你的 at_exit()
函数,这对于快照重置很有用,尤其是在程序可能有多个退出路径的情况下,因为你只需要覆盖一个退出点。
希望这对某些人有帮助!HOOK的完整代码在下面,祝模糊测试顺利!
本文使用chatGPT-4o翻译而成,如有错误之处,请斧正
原文链接:https://h0mbre.github.io/Fuzzing-Like-A-Caveman-6/
本篇预计为本系列最后一篇
stat(
"/bin/ls"
, {st_mode=S_IFREG|0755, st_size=133792, ...}) = 0
stat(
"/bin/ls"
, {st_mode=S_IFREG|0755, st_size=133792, ...}) = 0
openat(AT_FDCWD,
"/bin/ls"
, O_RDONLY) = 3
fcntl(3, F_GETFD) = 0
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
stat(
"/bin/ls"
, {st_mode=S_IFREG|0755, st_size=133792, ...}) = 0
stat(
"/bin/ls"
, {st_mode=S_IFREG|0755, st_size=133792, ...}) = 0
openat(AT_FDCWD,
"/bin/ls"
, O_RDONLY) = 3
fcntl(3, F_GETFD) = 0
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
read
(3,
"\0\0\0\0\0\0\0\0\10\0\"\0\0\0\0\0\1\0\0\0\377\377\377\377\1\0\0\0\0\0\0\0"
..., 4096) = 2720
write(1,
":(%rax)\n 21ffa4:\t00 00 "
..., 4096) = 4096
write(1,
"x0,%eax\n 220105:\t00 00 "
..., 4096) = 4096
close(3) = 0
write(1,
"023e:\t00 00 \tadd "
..., 2190) = 2190
exit_group(0) = ?
+++ exited with 0 +++
read
(3,
"\0\0\0\0\0\0\0\0\10\0\"\0\0\0\0\0\1\0\0\0\377\377\377\377\1\0\0\0\0\0\0\0"
..., 4096) = 2720
write(1,
":(%rax)\n 21ffa4:\t00 00 "
..., 4096) = 4096
write(1,
"x0,%eax\n 220105:\t00 00 "
..., 4096) = 4096
close(3) = 0
write(1,
"023e:\t00 00 \tadd "
..., 2190) = 2190
exit_group(0) = ?
+++ exited with 0 +++
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#include <stdio.h> /* printf */
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#include <stdio.h> /* printf */
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
h0mbre@ubuntu:~
/blogpost
$ LD_PRELOAD=
/home/h0mbre/blogpost/blog_harness
.so objdump -D
/bin/ls
>
/tmp/output
.txt &&
head
-n 20
/tmp/output
.txt
**> LD_PRELOAD shared object loaded!
/bin/ls
:
file
format
elf64-x86-64
Disassembly of section .interp:
0000000000000238 <.interp>:
238: 2f (bad)
239: 6c ins BYTE PTR es:[rdi],dx
23a: 69 62 36 34 2f 6c 64 imul esp,DWORD PTR [rdx+0x36],0x646c2f34
241: 2d 6c 69 6e 75 sub eax,0x756e696c
246: 78 2d js 275 <_init@@Base-0x34e3>
248: 78 38 js 282 <_init@@Base-0x34d6>
24a: 36 2d 36 34 2e 73 ss sub eax,0x732e3436
250: 6f outs dx,DWORD PTR ds:[rsi]
251: 2e 32 00 xor al,BYTE PTR cs:[rax]
Disassembly of section .note.ABI-tag:
h0mbre@ubuntu:~
/blogpost
$ LD_PRELOAD=
/home/h0mbre/blogpost/blog_harness
.so objdump -D
/bin/ls
>
/tmp/output
.txt &&
head
-n 20
/tmp/output
.txt
**> LD_PRELOAD shared object loaded!
/bin/ls
:
file
format
elf64-x86-64
Disassembly of section .interp:
0000000000000238 <.interp>:
238: 2f (bad)
239: 6c ins BYTE PTR es:[rdi],dx
23a: 69 62 36 34 2f 6c 64 imul esp,DWORD PTR [rdx+0x36],0x646c2f34
241: 2d 6c 69 6e 75 sub eax,0x756e696c
246: 78 2d js 275 <_init@@Base-0x34e3>
248: 78 38 js 282 <_init@@Base-0x34d6>
24a: 36 2d 36 34 2e 73 ss sub eax,0x732e3436
250: 6f outs dx,DWORD PTR ds:[rsi]
251: 2e 32 00 xor al,BYTE PTR cs:[rax]
Disassembly of section .note.ABI-tag:
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Declare a prototype for the real stat as a function pointer
typedef
int
(*stat_t)(
const
char
*restrict path,
struct
stat *restrict buf);
stat_t real_stat = NULL;
// Hook function, objdump will call this stat instead of the real one
int
stat(
const
char
*restrict path,
struct
stat *restrict buf) {
printf
(
"** stat() hook!\n"
);
exit
(0);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Declare a prototype for the real stat as a function pointer
typedef
int
(*stat_t)(
const
char
*restrict path,
struct
stat *restrict buf);
stat_t real_stat = NULL;
// Hook function, objdump will call this stat instead of the real one
int
stat(
const
char
*restrict path,
struct
stat *restrict buf) {
printf
(
"** stat() hook!\n"
);
exit
(0);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Hook function, objdump will call this stat instead of the real one
int
__xstat(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf) {
printf
(
"** Hit our __xstat() hook!\n"
);
exit
(0);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Hook function, objdump will call this stat instead of the real one
int
__xstat(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf) {
printf
(
"** Hit our __xstat() hook!\n"
);
exit
(0);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#define _GNU_SOURCE /* dlsym */
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
#include <dlfcn.h> /* dlsym and friends */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Returns memory address of *next* location of symbol in library search order
static
void
*_resolve_symbol(
const
char
*symbol) {
// Clear previous errors
dlerror();
// Get symbol address
void
* addr = dlsym(RTLD_NEXT, symbol);
// Check for error
char
* err = NULL;
err = dlerror();
if
(err) {
addr = NULL;
printf
(
"Err resolving '%s' addr: %s\n"
, symbol, err);
exit
(-1);
}
return
addr;
}
// Hook function, objdump will call this stat instead of the real one
int
__xstat(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf) {
// Print the filename requested
printf
(
"** __xstat() hook called for filename: '%s'\n"
, __filename);
// Resolve the address of the real __xstat() on demand and only once
if
(!real_xstat) {
real_xstat = _resolve_symbol(
"__xstat"
);
}
// Call the real __xstat() for the caller so everything keeps going
return
real_xstat(__ver, __filename, __stat_buf);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#define _GNU_SOURCE /* dlsym */
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
#include <dlfcn.h> /* dlsym and friends */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Returns memory address of *next* location of symbol in library search order
static
void
*_resolve_symbol(
const
char
*symbol) {
// Clear previous errors
dlerror();
// Get symbol address
void
* addr = dlsym(RTLD_NEXT, symbol);
// Check for error
char
* err = NULL;
err = dlerror();
if
(err) {
addr = NULL;
printf
(
"Err resolving '%s' addr: %s\n"
, symbol, err);
exit
(-1);
}
return
addr;
}
// Hook function, objdump will call this stat instead of the real one
int
__xstat(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf) {
// Print the filename requested
printf
(
"** __xstat() hook called for filename: '%s'\n"
, __filename);
// Resolve the address of the real __xstat() on demand and only once
if
(!real_xstat) {
real_xstat = _resolve_symbol(
"__xstat"
);
}
// Call the real __xstat() for the caller so everything keeps going
return
real_xstat(__ver, __filename, __stat_buf);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
printf
(
"** LD_PRELOAD shared object loaded!\n"
);
}
h0mbre@ubuntu:~/blogpost$ LD_PRELOAD=/home/h0mbre/blogpost/blog_harness.so objdump -D fuzzme > /tmp/output.txt && grep
"** __xstat"
/tmp/output.txt
** __xstat() hook called
for
filename:
'fuzzme'
** __xstat() hook called
for
filename:
'fuzzme'
h0mbre@ubuntu:~/blogpost$ LD_PRELOAD=/home/h0mbre/blogpost/blog_harness.so objdump -D fuzzme > /tmp/output.txt && grep
"** __xstat"
/tmp/output.txt
** __xstat() hook called
for
filename:
'fuzzme'
** __xstat() hook called
for
filename:
'fuzzme'
insert_fuzzcase(config.input_location, config.input_size_location, input, input_size) {
memcpy
(config.input_location, &input, input_size);
memcpy
(config.input_size_location, &input_size,
sizeof
(
size_t
));
}
insert_fuzzcase(config.input_location, config.input_size_location, input, input_size) {
memcpy
(config.input_location, &input, input_size);
memcpy
(config.input_size_location, &input_size,
sizeof
(
size_t
));
}
// Map memory to hold our inputs in memory and information about their size
static
void
_create_mem_mappings(
void
) {
void
*result = NULL;
// Map the page to hold the input size
result = mmap(
(
void
*)(INPUT_SZ_ADDR),
sizeof
(
size_t
),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_SZ_ADDR)) {
printf
(
"Err mapping INPUT_SZ_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Let's actually initialize the value at the input size location as well
*(
size_t
*)INPUT_SZ_ADDR = 0;
// Map the pages to hold the input contents
result = mmap(
(
void
*)(INPUT_ADDR),
(
size_t
)(MAX_INPUT_SZ),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_ADDR)) {
printf
(
"Err mapping INPUT_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Init the value
memset
((
void
*)INPUT_ADDR, 0, (
size_t
)MAX_INPUT_SZ);
}
// Map memory to hold our inputs in memory and information about their size
static
void
_create_mem_mappings(
void
) {
void
*result = NULL;
// Map the page to hold the input size
result = mmap(
(
void
*)(INPUT_SZ_ADDR),
sizeof
(
size_t
),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_SZ_ADDR)) {
printf
(
"Err mapping INPUT_SZ_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Let's actually initialize the value at the input size location as well
*(
size_t
*)INPUT_SZ_ADDR = 0;
// Map the pages to hold the input contents
result = mmap(
(
void
*)(INPUT_ADDR),
(
size_t
)(MAX_INPUT_SZ),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_ADDR)) {
printf
(
"Err mapping INPUT_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Init the value
memset
((
void
*)INPUT_ADDR, 0, (
size_t
)MAX_INPUT_SZ);
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#define _GNU_SOURCE /* dlsym */
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
#include <dlfcn.h> /* dlsym and friends */
#include <sys/mman.h> /* mmap */
#include <string.h> /* memset */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Definitions for our in-memory inputs
#define INPUT_SZ_ADDR 0x1336000
#define INPUT_ADDR 0x1337000
#define MAX_INPUT_SZ (1024 * 1024)
// Our "legit" global stat struct
struct
stat st;
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Returns memory address of *next* location of symbol in library search order
static
void
*_resolve_symbol(
const
char
*symbol) {
// Clear previous errors
dlerror();
// Get symbol address
void
* addr = dlsym(RTLD_NEXT, symbol);
// Check for error
char
* err = NULL;
err = dlerror();
if
(err) {
addr = NULL;
printf
(
"Err resolving '%s' addr: %s\n"
, symbol, err);
exit
(-1);
}
return
addr;
}
// Hook for __xstat
int
__xstat(
int
__ver,
const
char
* __filename,
struct
stat* __stat_buf) {
// Resolve the real __xstat() on demand and maybe multiple times!
if
(NULL == real_xstat) {
real_xstat = _resolve_symbol(
"__xstat"
);
}
// Assume the worst, always
int
ret = -1;
// Special __ver value check to see if we're calling from constructor
if
(0x1337 == __ver) {
// Patch back up the version value before sending to real xstat
__ver = 1;
ret = real_xstat(__ver, __filename, __stat_buf);
// Set the real_xstat back to NULL
real_xstat = NULL;
return
ret;
}
// Determine if we're stat'ing our fuzzing target
if
(!
strcmp
(__filename, FUZZ_TARGET)) {
// Update our global stat struct
st.st_size = *(
size_t
*)INPUT_SZ_ADDR;
// Send it back to the caller, skip syscall
memcpy
(__stat_buf, &st,
sizeof
(
struct
stat));
ret = 0;
}
// Just a normal stat, send to real xstat
else
{
ret = real_xstat(__ver, __filename, __stat_buf);
}
return
ret;
}
// Map memory to hold our inputs in memory and information about their size
static
void
_create_mem_mappings(
void
) {
void
*result = NULL;
// Map the page to hold the input size
result = mmap(
(
void
*)(INPUT_SZ_ADDR),
sizeof
(
size_t
),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_SZ_ADDR)) {
printf
(
"Err mapping INPUT_SZ_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Let's actually initialize the value at the input size location as well
*(
size_t
*)INPUT_SZ_ADDR = 0;
// Map the pages to hold the input contents
result = mmap(
(
void
*)(INPUT_ADDR),
(
size_t
)(MAX_INPUT_SZ),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_ADDR)) {
printf
(
"Err mapping INPUT_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Init the value
memset
((
void
*)INPUT_ADDR, 0, (
size_t
)MAX_INPUT_SZ);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
// Create memory mappings to hold our input and information about its size
_create_mem_mappings();
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#define _GNU_SOURCE /* dlsym */
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
#include <dlfcn.h> /* dlsym and friends */
#include <sys/mman.h> /* mmap */
#include <string.h> /* memset */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Definitions for our in-memory inputs
#define INPUT_SZ_ADDR 0x1336000
#define INPUT_ADDR 0x1337000
#define MAX_INPUT_SZ (1024 * 1024)
// Our "legit" global stat struct
struct
stat st;
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Returns memory address of *next* location of symbol in library search order
static
void
*_resolve_symbol(
const
char
*symbol) {
// Clear previous errors
dlerror();
// Get symbol address
void
* addr = dlsym(RTLD_NEXT, symbol);
// Check for error
char
* err = NULL;
err = dlerror();
if
(err) {
addr = NULL;
printf
(
"Err resolving '%s' addr: %s\n"
, symbol, err);
exit
(-1);
}
return
addr;
}
// Hook for __xstat
int
__xstat(
int
__ver,
const
char
* __filename,
struct
stat* __stat_buf) {
// Resolve the real __xstat() on demand and maybe multiple times!
if
(NULL == real_xstat) {
real_xstat = _resolve_symbol(
"__xstat"
);
}
// Assume the worst, always
int
ret = -1;
// Special __ver value check to see if we're calling from constructor
if
(0x1337 == __ver) {
// Patch back up the version value before sending to real xstat
__ver = 1;
ret = real_xstat(__ver, __filename, __stat_buf);
// Set the real_xstat back to NULL
real_xstat = NULL;
return
ret;
}
// Determine if we're stat'ing our fuzzing target
if
(!
strcmp
(__filename, FUZZ_TARGET)) {
// Update our global stat struct
st.st_size = *(
size_t
*)INPUT_SZ_ADDR;
// Send it back to the caller, skip syscall
memcpy
(__stat_buf, &st,
sizeof
(
struct
stat));
ret = 0;
}
// Just a normal stat, send to real xstat
else
{
ret = real_xstat(__ver, __filename, __stat_buf);
}
return
ret;
}
// Map memory to hold our inputs in memory and information about their size
static
void
_create_mem_mappings(
void
) {
void
*result = NULL;
// Map the page to hold the input size
result = mmap(
(
void
*)(INPUT_SZ_ADDR),
sizeof
(
size_t
),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_SZ_ADDR)) {
printf
(
"Err mapping INPUT_SZ_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Let's actually initialize the value at the input size location as well
*(
size_t
*)INPUT_SZ_ADDR = 0;
// Map the pages to hold the input contents
result = mmap(
(
void
*)(INPUT_ADDR),
(
size_t
)(MAX_INPUT_SZ),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_ADDR)) {
printf
(
"Err mapping INPUT_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Init the value
memset
((
void
*)INPUT_ADDR, 0, (
size_t
)MAX_INPUT_SZ);
}
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
// Create memory mappings to hold our input and information about its size
_create_mem_mappings();
}
h0mbre@ubuntu:~
/blogpost
$ LD_PRELOAD=
/home/h0mbre/blogpost/blog_harness
.so objdump -D fuzzme
objdump: Warning:
'fuzzme'
is not an ordinary
file
h0mbre@ubuntu:~
/blogpost
$ LD_PRELOAD=
/home/h0mbre/blogpost/blog_harness
.so objdump -D fuzzme
objdump: Warning:
'fuzzme'
is not an ordinary
file
gcc -D TEST -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ld
gcc -D TEST -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ld
// Create a "legit" stat struct globally to pass to callers
static
void
_setup_stat_struct(
void
) {
// Create a global stat struct for our file in case someone asks, this way
// when someone calls stat() or fstat() on our target, we can just return the
// slightly altered (new size) stat struct &skip the kernel, save syscalls
int
result = __xstat(0x1337, FUZZ_TARGET, &st);
if
(-1 == result) {
printf
(
"Error creating stat struct for '%s' during load\n"
, FUZZ_TARGET);
}
}
// Create a "legit" stat struct globally to pass to callers
static
void
_setup_stat_struct(
void
) {
// Create a global stat struct for our file in case someone asks, this way
// when someone calls stat() or fstat() on our target, we can just return the
// slightly altered (new size) stat struct &skip the kernel, save syscalls
int
result = __xstat(0x1337, FUZZ_TARGET, &st);
if
(-1 == result) {
printf
(
"Error creating stat struct for '%s' during load\n"
, FUZZ_TARGET);
}
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#define _GNU_SOURCE /* dlsym */
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
#include <dlfcn.h> /* dlsym and friends */
#include <sys/mman.h> /* mmap */
#include <string.h> /* memset */
#include <fcntl.h> /* open */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Definitions for our in-memory inputs
#define INPUT_SZ_ADDR 0x1336000
#define INPUT_ADDR 0x1337000
#define MAX_INPUT_SZ (1024 * 1024)
// For testing purposes, we read /bin/ed into our input buffer to simulate
// what the fuzzer would do
#define TEST_FILE "/bin/ed"
// Our "legit" global stat struct
struct
stat st;
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Returns memory address of *next* location of symbol in library search order
static
void
*_resolve_symbol(
const
char
*symbol) {
// Clear previous errors
dlerror();
// Get symbol address
void
* addr = dlsym(RTLD_NEXT, symbol);
// Check for error
char
* err = NULL;
err = dlerror();
if
(err) {
addr = NULL;
printf
(
"Err resolving '%s' addr: %s\n"
, symbol, err);
exit
(-1);
}
return
addr;
}
// Hook for __xstat
int
__xstat(
int
__ver,
const
char
* __filename,
struct
stat* __stat_buf) {
// Resolve the real __xstat() on demand and maybe multiple times!
if
(!real_xstat) {
real_xstat = _resolve_symbol(
"__xstat"
);
}
// Assume the worst, always
int
ret = -1;
// Special __ver value check to see if we're calling from constructor
if
(0x1337 == __ver) {
// Patch back up the version value before sending to real xstat
__ver = 1;
ret = real_xstat(__ver, __filename, __stat_buf);
// Set the real_xstat back to NULL
real_xstat = NULL;
return
ret;
}
// Determine if we're stat'ing our fuzzing target
if
(!
strcmp
(__filename, FUZZ_TARGET)) {
// Update our global stat struct
st.st_size = *(
size_t
*)INPUT_SZ_ADDR;
// Send it back to the caller, skip syscall
memcpy
(__stat_buf, &st,
sizeof
(
struct
stat));
ret = 0;
}
// Just a normal stat, send to real xstat
else
{
ret = real_xstat(__ver, __filename, __stat_buf);
}
return
ret;
}
// Map memory to hold our inputs in memory and information about their size
static
void
_create_mem_mappings(
void
) {
void
*result = NULL;
// Map the page to hold the input size
result = mmap(
(
void
*)(INPUT_SZ_ADDR),
sizeof
(
size_t
),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_SZ_ADDR)) {
printf
(
"Err mapping INPUT_SZ_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Let's actually initialize the value at the input size location as well
*(
size_t
*)INPUT_SZ_ADDR = 0;
// Map the pages to hold the input contents
result = mmap(
(
void
*)(INPUT_ADDR),
(
size_t
)(MAX_INPUT_SZ),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_ADDR)) {
printf
(
"Err mapping INPUT_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Init the value
memset
((
void
*)INPUT_ADDR, 0, (
size_t
)MAX_INPUT_SZ);
}
// Create a "legit" stat struct globally to pass to callers
static
void
_setup_stat_struct(
void
) {
int
result = __xstat(0x1337, FUZZ_TARGET, &st);
if
(-1 == result) {
printf
(
"Error creating stat struct for '%s' during load\n"
, FUZZ_TARGET);
}
}
// Used for testing, load /bin/ed into the input buffer and update its size info
#ifdef TEST
static
void
_test_func(
void
) {
// Open TEST_FILE for reading
int
fd = open(TEST_FILE, O_RDONLY);
if
(-1 == fd) {
printf
(
"Failed to open '%s' during test\n"
, TEST_FILE);
exit
(-1);
}
// Attempt to read max input buf size
ssize_t bytes = read(fd, (
void
*)INPUT_ADDR, (
size_t
)MAX_INPUT_SZ);
close(fd);
// Update the input size
*(
size_t
*)INPUT_SZ_ADDR = (
size_t
)bytes;
}
#endif
// Routine to be called when our shared object is loaded
__attribute__((constructor))
static
void
_hook_load(
void
) {
// Create memory mappings to hold our input and information about its size
_create_mem_mappings();
// Setup global "legit" stat struct
_setup_stat_struct();
// If we're testing, load /bin/ed up into our input buffer and update size
#ifdef TEST
_test_func();
#endif
}
/*
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/
#define _GNU_SOURCE /* dlsym */
#include <stdio.h> /* printf */
#include <sys/stat.h> /* stat */
#include <stdlib.h> /* exit */
#include <unistd.h> /* __xstat, __fxstat */
#include <dlfcn.h> /* dlsym and friends */
#include <sys/mman.h> /* mmap */
#include <string.h> /* memset */
#include <fcntl.h> /* open */
// Filename of the input file we're trying to emulate
#define FUZZ_TARGET "fuzzme"
// Definitions for our in-memory inputs
#define INPUT_SZ_ADDR 0x1336000
#define INPUT_ADDR 0x1337000
#define MAX_INPUT_SZ (1024 * 1024)
// For testing purposes, we read /bin/ed into our input buffer to simulate
// what the fuzzer would do
#define TEST_FILE "/bin/ed"
// Our "legit" global stat struct
struct
stat st;
// Declare a prototype for the real stat as a function pointer
typedef
int
(*__xstat_t)(
int
__ver,
const
char
*__filename,
struct
stat *__stat_buf);
__xstat_t real_xstat = NULL;
// Returns memory address of *next* location of symbol in library search order
static
void
*_resolve_symbol(
const
char
*symbol) {
// Clear previous errors
dlerror();
// Get symbol address
void
* addr = dlsym(RTLD_NEXT, symbol);
// Check for error
char
* err = NULL;
err = dlerror();
if
(err) {
addr = NULL;
printf
(
"Err resolving '%s' addr: %s\n"
, symbol, err);
exit
(-1);
}
return
addr;
}
// Hook for __xstat
int
__xstat(
int
__ver,
const
char
* __filename,
struct
stat* __stat_buf) {
// Resolve the real __xstat() on demand and maybe multiple times!
if
(!real_xstat) {
real_xstat = _resolve_symbol(
"__xstat"
);
}
// Assume the worst, always
int
ret = -1;
// Special __ver value check to see if we're calling from constructor
if
(0x1337 == __ver) {
// Patch back up the version value before sending to real xstat
__ver = 1;
ret = real_xstat(__ver, __filename, __stat_buf);
// Set the real_xstat back to NULL
real_xstat = NULL;
return
ret;
}
// Determine if we're stat'ing our fuzzing target
if
(!
strcmp
(__filename, FUZZ_TARGET)) {
// Update our global stat struct
st.st_size = *(
size_t
*)INPUT_SZ_ADDR;
// Send it back to the caller, skip syscall
memcpy
(__stat_buf, &st,
sizeof
(
struct
stat));
ret = 0;
}
// Just a normal stat, send to real xstat
else
{
ret = real_xstat(__ver, __filename, __stat_buf);
}
return
ret;
}
// Map memory to hold our inputs in memory and information about their size
static
void
_create_mem_mappings(
void
) {
void
*result = NULL;
// Map the page to hold the input size
result = mmap(
(
void
*)(INPUT_SZ_ADDR),
sizeof
(
size_t
),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
);
if
((MAP_FAILED == result) || (result != (
void
*)INPUT_SZ_ADDR)) {
printf
(
"Err mapping INPUT_SZ_ADDR, mapped @ %p\n"
, result);
exit
(-1);
}
// Let's actually initialize the value at the input size location as well
*(
size_t
*)INPUT_SZ_ADDR = 0;
// Map the pages to hold the input contents
result = mmap(
(
void
*)(INPUT_ADDR),
(
size_t
)(MAX_INPUT_SZ),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0,
0
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!