野蛮fuzz:持久性fuzz

谈思实验室 2024-11-24 18:00

点击上方蓝字谈思实验室

获取更多汽车网络安全资讯

在初学者模糊测试圈子中(显然我也是其中一员),最常见的问题之一是如何HOOK目标,使其能够在内存中进行模糊测试,有些人称之为“持久性”模糊测试,以获得更高的性能。持久性模糊测试有一个特定的用例,即当目标在不同的模糊测试用例之间不涉及太多全局状态时,这种方式会很有用。一个例子是对库中的单个API或二进制文件中的单个函数进行紧密的模糊测试循环。


这种模糊测试风格比一次又一次从头开始重新执行目标要快,因为我们绕过了与创建和销毁任务结构相关的所有繁重的系统调用/内核例程。


然而,对于没有源代码的二进制目标来说,在不进行深入逆向工程的情况下(真恶心,工作?呃),有时很难辨别在执行任何代码路径时我们影响了哪些全局状态。此外,我们通常希望模糊测试更广泛的循环。模糊测试一个返回一个结构体的函数并没有多大帮助,因为该结构体在我们的模糊测试工作流程中从未被读取或使用。考虑到这些情况,我们通常发现“快照”模糊测试对于二进制目标,甚至是那些我们有源代码但经过企业构建系统处理的生产二进制文件来说,是一种更稳健的工作流程。


因此,今天我们将学习如何将一个任意的仅二进制目标(该目标从用户那里接收输入文件)转换为一个从内存中接收输入的目标,并使其能够在不同的模糊测试用例之间重置状态。


01

目标(简单模式)

在这篇博客文章中,我们将使用objdump作为快照模糊测试的HOOK目标。这能够满足我们的需求,因为它相对简单(单线程、单进程),并且它是一个常见的模糊测试目标,尤其是在人们开发模糊测试工具时。本文的重点不是通过沙盒化像Chrome这样复杂的目标来打动你,而是向初学者展示如何开始思考HOOK目标。你需要将你的目标改造得面目全非,但保持相同的语义。你可以尽情发挥创造力,坦白说,有时候HOOK目标是与模糊测试相关的最令人满意的工作之一。成功地将目标沙盒化,并使其与模糊测试器良好配合,感觉非常棒。那么我们开始吧。


Hello World

第一步是确定我们想如何改变objdump的行为。让我们尝试在strace下运行它,并反汇编ls,看看它在系统调用级别上如何表现,命令为strace objdump -D /bin/ls。我们要寻找的是objdump开始与我们的输入(在本例中为/bin/ls)交互的地方。在输出中,如果你滚动浏览过那些样板内容,你可以看到/bin/ls的首次出现:


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


请记住,在阅读本文时,如果你在家里跟随操作,你的输出可能不会与我的完全匹配。我可能运行的是与你不同的发行版以及不同版本的objdump。但这篇博客文章的目的是展示一些概念,你可以自行发挥创意。


我还注意到,程序在执行结束前并不会关闭我们的输入文件:


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 +++


这点很重要,我们需要让我们的HOOK程序能很好地模拟一个输入文件,因为objdump并不会一次性将文件读入内存缓冲区或使用mmap()映射输入文件。它会在整个strace输出过程中不断地从文件中读取数据。


由于我们没有目标的源代码,我们将通过使用LD_PRELOAD共享对象来影响其行为。通过使用LD_PRELOAD共享对象,我们应该能够HOOK与输入文件交互的系统调用的包装函数,并修改它们的行为以满足我们的需求。如果你不熟悉动态链接或LD_PRELOAD,现在是一个很好的时机去搜索更多信息,这是一个很好的起点。首先,我们先加载一个“Hello, World!”的共享对象。


我们可以利用gcc的函数属性(Function Attributes)来在共享对象被目标加载时执行代码,通过构造器属性(constructor attribute)来实现。


因此,迄今为止我们的代码将如下所示:


/* 
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/


#include /* 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");
}


我将编译所需的标志添加到了文件顶部作为注释。这些标志来自于我之前阅读的关于使用LD_PRELOAD共享对象的博客文章:https://tbrindus.ca/correct-ld-preload-hooking-libc/。


现在我们可以使用LD_PRELOAD环境变量,并运行带有我们的共享对象的objdump,它应该在加载时打印信息:


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:


它有效,现在我们可以开始寻找需要HOOK的函数了。


寻找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, )来获取它的位置,并将指针值更改为真实符号地址。(稍后这将变得更清晰)。


现在我们需要HOOKstat(),它在man 3中作为一个条目(意味着它是一个libc导出的函数),同时也在man 2中作为一个条目(意味着它是一个系统调用)。这让我困惑了很长时间,因为这个命名冲突,我经常误解系统调用的实际工作原理。你可以阅读我最早的研究博客文章之一,在那里这种困惑显而易见,我也经常做出错误的断言。(顺便说一下,我永远不会编辑那些有错误的旧博客文章,它们就像时间胶囊,对我来说这很酷)。


我们想要编写一个函数,当被调用时,只需打印一些信息并退出,以便我们知道我们的HOOK被命中了。目前,我们的代码如下所示:


/* 
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/


#include /* printf */
#include /* stat */
#include /* 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");
}


然而,如果我们编译并运行该代码,我们并没有打印任何内容并退出,所以我们的HOOK没有被调用。出了点问题。有时候,libc中与文件相关的函数有 64 位变体,比如open()open64(),它们会根据配置和标志的不同而交替使用。我尝试HOOKstat64(),但仍然没有成功。


幸运的是,我并不是第一个遇到这个问题的人。Stackoverflow 上有一个很棒的答案,讨论了这个问题,解释了libc并没有像其他函数(如open()open64())那样导出stat(),而是导出了一个叫做__xstat()的符号。这个符号具有略微不同的签名,并需要一个名为version的新参数,用于描述调用者期望的stat结构体的版本。这一切本应在底层自动处理,但我们现在必须自己让这些“魔法”发挥作用。对于lstat()fstat()也是同样的规则,它们分别有__lxstat()__fxstat()


我在这里找到了这些函数的定义。于是我们可以将__xstat()HOOK添加到我们的共享对象中,替换掉stat(),看看是否有不同的结果。现在我们的代码如下所示:


/* 
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/


#include /* printf */
#include /* stat */
#include /* exit */
#include /* __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");
}


现在,如果我们运行我们的共享对象,我们得到了预期的结果,某处我们的HOOK被命中。现在我们可以帮自己一把,打印出HOOK请求的文件名,然后实际上代表调用者调用真实的__xstat()。当我们的HOOK被命中时,我们需要通过名称解析真正的__xstat()的位置,所以我们会向共享对象中添加一个符号解析函数。现在我们的共享对象代码如下所示:


/* 
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/


#define _GNU_SOURCE /* dlsym */
#include /* printf */
#include /* stat */
#include /* exit */
#include /* __xstat, __fxstat */
#include /* 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'


现在我们可以开始享受过程了。 

__xstat()HOOK

这个HOOK的目的是欺骗objdump,让它以为成功地对输入文件执行了stat()。记住,我们正在制作一个快照模糊测试HOOK,所以我们的目标是不断地创建新输入并通过这个HOOK传递给objdump。最重要的是,我们的HOOK需要能够将我们存储在内存中的可变长度输入表示为文件。每个模糊测试用例中,文件长度可能会发生变化,而我们的HOOK需要适应这一点。


此时我的想法是创建一个看起来“合法”的stat结构体,它通常会为我们的实际文件fuzzme(只是/bin/ls的副本)返回。我们可以全局存储这个stat结构体,并在每个新的模糊测试用例中只更新大小字段。因此,我们的快照模糊测试工作流程的时间线可能如下所示:


1.当我们的共享对象被加载时,构造函数被调用。

2.构造函数设置一个全局的“合法”stat结构体,我们可以为每个模糊测试用例更新这个结构体,并将其传递给试图对我们的模糊测试目标执行stat()__xstat()调用者。

3.假想的模糊测试器运行objdump到达快照位置。

4.我们的__xstat()HOOK更新全局“合法”stat结构体的大小字段,并将stat结构体复制到调用者的缓冲区中。

5.假想的模糊测试器将objdump的状态恢复到快照时的状态。

6.假想的模糊测试器将新的输入复制到HOOK,并更新输入大小。

7.我们的__xstat()HOOK再次被调用,我们重复步骤 4,这个过程会无休止地进行下去。


因此,我们可以想象模糊测试器有类似这样的伪代码例程,尽管它可能是跨进程的,并且需要process_vm_writev


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));
}


一个重要的事项是,如果快照模糊测试器在每次模糊测试迭代时都将objdump恢复到其快照状态,我们必须小心不要依赖任何全局的可变内存。全局的stat结构体是安全的,因为它将在构造函数期间实例化,然而,模糊测试器的快照恢复例程将在每次模糊测试迭代时将其size字段恢复到原始值。


我们还需要一个全局的、可识别的地址来存储可变的全局数据,比如当前输入的大小。几个快照模糊测试器具有忽略连续内存范围以用于恢复目的的灵活性。因此,如果我们能够在可识别的地址上创建一些连续的内存缓冲区,我们可以让我们的假想模糊测试器在快照恢复时忽略这些内存范围。因此,我们需要有一个地方来存储输入,以及有关其大小的信息。然后我们可以以某种方式告诉模糊测试器这些位置,当它生成新输入时,它会将其复制到输入位置,然后更新当前输入大小信息。


所以现在我们的构造函数有了一个额外的任务:设置输入位置以及输入大小信息。我们可以通过调用mmap()来轻松实现这一点,这使我们能够通过MAP_FIXED标志指定我们希望将映射映射到的地址。我们还会创建一个MAX_INPUT_SZ定义,以便我们知道从输入位置映射多少内存。


仅仅是与映射输入本身及其大小信息相关的函数看起来是这样的。请注意,我们使用了MAP_FIXED并检查了mmap()的返回地址,以确保调用成功但没有将我们的内存映射到不同的位置:


// 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);
}


mmap()实际上会映射系统上页面大小的倍数(通常是 4096 字节)。所以,当我们要求映射sizeof(size_t)字节时,mmap()就像:“嗯,这只是一页,兄弟”,并且从0x13360000x1337000给了我们一整页(不包括高端)。


随机附带提一下,在定义和宏中进行算术运算时要小心,就像我在这里用MAX_INPUT_SIZE所做的那样,预处理器很容易将你的文本替换为定义关键字,从而破坏一些操作顺序,甚至溢出特定的基本类型,如int


现在我们已经为模糊测试器设置了存储输入及其大小信息的内存,我们可以创建那个全局的stat结构体了。但是我们实际上遇到了一个大问题。如果我们已经HOOK了__xstat(),我们如何调用__xstat()以获取我们的“合法”stat结构体呢?我们会命中自己的HOOK。为了解决这个问题,我们可以使用一个特殊的__ver参数调用__xstat(),我们知道这意味着它是从构造函数中调用的,变量是一个int类型,所以我们可以使用0x1337作为特殊值。


这样,在我们的HOOK中,如果我们检查__ver并且它是0x1337,我们就知道它是从构造函数中调用的,我们可以实际对我们的真实文件进行stat调用并创建一个全局的“合法”stat结构体。当我转储objdump__xstat()的正常调用时,__version总是值1,所以我们会在HOOK中将其修补回去。现在我们整个共享对象的源文件应该如下所示:


/* 
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/


#define _GNU_SOURCE /* dlsym */
#include /* printf */
#include /* stat */
#include /* exit */
#include /* __xstat, __fxstat */
#include /* dlsym and friends */
#include /* mmap */
#include /* 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


这很酷,这意味着objdump的开发人员做了一些正确的事情,他们的stat()会说:“嘿,这个文件长度为零,发生了什么怪事。”然后他们会输出这个错误消息并退出。干得好,开发者们!


所以我们已经确定了一个问题,我们需要模拟模糊测试器将一个真实的输入放入内存中,为此,我将开始使用#ifdef来定义我们是否在测试我们的共享对象。所以基本上,如果我们编译共享对象并定义TEST,我们的共享对象将复制一个“输入”到内存中,以模拟模糊测试器在模糊测试期间的行为,我们可以看看我们的HOOK是否工作得当。所以如果我们定义了TEST,我们将把/bin/ed复制到内存中,并更新我们的全局“合法”stat结构体的大小成员,并将/bin/ed的字节放入内存中。


你现在可以按照如下方式编译共享对象以进行测试:


gcc -D TEST -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ld


我们还需要设置我们的全局“合法”stat结构体,代码应如下所示。记住,我们传递一个假的__ver变量让__xstat()HOOK知道是在构造函数例程中调用的,从而允许HOOK正常运行并为我们提供所需的stat结构体:


// 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);
}
}


总的来说,我们的整个HOOK现在看起来是这样的:


/* 
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/


#define _GNU_SOURCE /* dlsym */
#include /* printf */
#include /* stat */
#include /* exit */
#include /* __xstat, __fxstat */
#include /* dlsym and friends */
#include /* mmap */
#include /* memset */
#include /* 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
}


现在,如果我们在strace下运行这个程序,我们注意到我们的两个stat()调用明显不见了。


close(3)                                = 0
openat(AT_FDCWD, "fuzzme", O_RDONLY) = 3
fcntl(3, F_GETFD) = 0
fcntl(3, F_SETFD, FD_CLOEXEC) = 0


我们不再看到openat()之前的stat()调用,并且程序没有在任何显著的地方崩溃。因此,这个HOOK似乎工作得很好。我们现在需要处理openat(),并确保我们实际上不与输入文件交互,而是欺骗objdump与内存中的输入交互。


寻找HOOKopenat()的方法

我的非专家直觉告诉我,可能有几种方式使得libc函数在底层调用openat()。这些方式可能包括包装函数open()以及fopen()。我们还需要注意它们的 64 位变体(open64()fopen64())。我决定首先尝试HOOKfopen()


// Declare prototype for the real fopen and its friend fopen64 
typedef FILE* (*fopen_t)(const char* pathname, const char* mode);
fopen_t real_fopen = NULL;

typedef FILE* (*fopen64_t)(const char* pathname, const char* mode);
fopen64_t real_fopen64 = NULL;

...

// Exploratory hooks to see if we're using fopen() related functions to open
// our input file
FILE* fopen(const char* pathname, const char* mode) {
printf("** fopen() called for '%s'\n", pathname);
exit(0);
}

FILE* fopen64(const char* pathname, const char* mode) {
printf("** fopen64() called for '%s'\n", pathname);
exit(0);
}


如果我们编译并运行我们的探索性HOOK,我们得到以下输出:


h0mbre@ubuntu:~/blogpost$ LD_PRELOAD=/home/h0mbre/blogpost/blog_harness.so objdump -D fuzzme
** fopen64() called for 'fuzzme'


Bingo,成功找到目标。


所以现在我们可以稍微完善一下这个HOOK函数,使其按我们的预期运行。


改进fopen64()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应该看起来如下所示:


// Our fopen hook, return a FILE* to the caller, also, if we are opening our
// target make sure we're not able to write to the file
FILE* fopen64(const char* pathname, const char* mode) {
// Resolve symbol on demand and only once
if (NULL == real_fopen64) {
real_fopen64 = _resolve_symbol("fopen64");
}

// Check to see what file we're opening
FILE* ret = NULL;
if (!strcmp(FUZZ_TARGET, pathname)) {
// We're trying to open our file, make sure it's a read-only mode
if (strcmp(mode, "r")) {
printf("Attempt to open fuzz-target in illegal mode: '%s'\n", mode);
exit(-1);
}

// Open shared memory FILE* and return to caller
ret = fmemopen((void*)INPUT_ADDR, *(size_t*)INPUT_SZ_ADDR, mode);

// Make sure we've never fopen()'d our fuzzing target before
if (faked_fp) {
printf("Attempting to fopen64() fuzzing target more than once\n");
exit(-1);
}

// Update faked_fp
faked_fp = ret;

// Change the filedes to something we know
ret->_fileno = 1337;
}

// We're not opening our file, send to regular fopen
else {
ret = real_fopen64(pathname, mode);
}

// Return FILE stream ptr to caller
return ret;
}


你可以看到我们:


◆如果符号位置尚未解析,则解析它

◆检查我们是否正在对模糊测试目标输入文件进行调用

◆调用fmemopen()并打开内存缓冲区,其中包含我们当前的输入及其大小


你可能还注意到了一些安全检查,以确保事情不会被忽视。我们有一个全局变量FILE *faked_fp,我们将其初始化为NULL,这让我们知道是否多次打开了我们的输入(在后续尝试打开时它将不再是NULL)。


我们还检查了mode参数,以确保我们得到的是一个只读的FILE *。我们不希望objdump修改我们的输入或以任何方式写入它,如果它试图这样做,我们需要知道。


此时运行我们的共享对象会产生以下输出:


h0mbre@ubuntu:~/blogpost$ LD_PRELOAD=/home/h0mbre/blogpost/blog_harness.so objdump -D fuzzme
objdump: fuzzme: Bad file descriptor


我的直觉告诉我,有什么东西试图与文件描述符 1337 进行交互。让我们再次在strace下运行,看看会发生什么。


h0mbre@ubuntu:~/blogpost$ strace -E LD_PRELOAD=/home/h0mbre/blogpost/blog_harness.so objdump -D fuzzme > /tmp/output.txt


在输出中,我们可以看到一些系统调用fcntl()fstat()都是用文件描述符 1337 调用的,该描述符显然不存在于我们的objdump进程中,因此我们已经能够找到问题所在。


fcntl(1337, F_GETFD)                    = -1 EBADF (Bad file descriptor)
prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=4*1024, rlim_max=4*1024}) = 0
fstat(1337, 0x7fff4bf54c90) = -1 EBADF (Bad file descriptor)
fstat(1337, 0x7fff4bf54bf0) = -1 EBADF (Bad file descriptor)


正如我们已经了解到的,libc中没有直接导出的fstat(),它像stat()一样是那种奇怪的函数,我们实际上必须HOOK__fxstat()。所以让我们尝试HOOK它,看看它是否会被调用用于我们的 1337 文件描述符。HOOK函数的初始代码如下:


// Declare prototype for the real __fxstat
typedef int (*__fxstat_t)(int __ver, int __filedesc, struct stat *__stat_buf);
__fxstat_t real_fxstat = NULL;

...

// Hook for __fxstat
int __fxstat (int __ver, int __filedesc, struct stat *__stat_buf) {
printf("** __fxstat() called for __filedesc: %d\n", __filedesc);
exit(0);
}


现在我们还需要处理fcntl(),幸运的是,这个HOOK比较简单。如果有人请求F_GETFD(即与那个特殊的 1337 文件描述符关联的标志),我们只需返回O_RDONLY,因为它是以这些标志“打开”的,如果有人为不同的文件描述符调用它,我们暂时只会触发一个 panic。这个HOOK如下所示:


// Declare prototype for the real __fcntl
typedef int (*fcntl_t)(int fildes, int cmd, ...);
fcntl_t real_fcntl = NULL;

...

// Hook for fcntl
int fcntl(int fildes, int cmd, ...) {
// Resolve fcntl symbol if needed
if (NULL == real_fcntl) {
real_fcntl = _resolve_symbol("fcntl");
}

if (fildes == 1337) {
return O_RDONLY;
}

else {
printf("** fcntl() called for real file descriptor\n");
exit(0);
}
}


现在在strace下运行时,fcntl()调用如预期那样消失了:


openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=26376, ...}) = 0
mmap(NULL, 26376, PROT_READ, MAP_SHARED, 3, 0) = 0x7ff61d331000
close(3) = 0
prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=4*1024, rlim_max=4*1024}) = 0
fstat(1, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
write(1, "** __fxstat() called for __filed"..., 42) = 42
exit_group(0) = ?
+++ exited with 0 +++


现在我们可以完善我们的__fxstat()HOOK逻辑。调用者希望通过传递特殊的文件描述符 1337 从函数中获取一个针对模糊测试目标fuzzmestat结构体。幸运的是,我们有一个全局的stat结构体,在我们更新其大小以匹配内存中当前输入的大小(由我们和模糊测试器通过INPUT_SIZE_ADDR处的值进行跟踪)之后,我们可以返回这个结构体。因此,如果被调用,我们只需更新stat结构体的大小,并将我们的结构体通过memcpy复制到调用者的*__stat_buf中。我们完整的HOOK代码现在如下:


// Hook for __fxstat
int __fxstat (int __ver, int __filedesc, struct stat *__stat_buf) {
// Resolve the real fxstat
if (NULL == real_fxstat) {
real_fxstat = _resolve_symbol("__fxstat");
}

int ret = -1;

// Check to see if we're stat'ing our fuzz target
if (1337 == __filedesc) {
// Patch the global struct with current input size
st.st_size = *(size_t*)INPUT_SZ_ADDR;

// Copy global stat struct back to caller
memcpy(__stat_buf, &st, sizeof(struct stat));
ret = 0;
}

// Normal stat, send to real fxstat
else {
ret = real_fxstat(__ver, __filedesc, __stat_buf);
}

return ret;
}


现在,如果我们运行这个代码,程序实际上不会崩溃,并且objdump可以在strace下干净地退出。


02

总结


为了测试我们是否做得不错,我们将输出objdump -D fuzzme到一个文件中,然后我们将加载我们的HOOK共享对象并输出相同的命令。最后,我们将运行objdump -D /bin/ed并输出到一个文件中,以查看我们的HOOK是否生成了相同的输出。


h0mbre@ubuntu:~/blogpost$ objdump -D fuzzme > /tmp/fuzzme_original.txt      
h0mbre@ubuntu:~/blogpost$ LD_PRELOAD=/home/h0mbre/blogpost/blog_harness.so objdump -D fuzzme > /tmp/harness.txt
h0mbre@ubuntu:~/blogpost$ objdump -D /bin/ed > /tmp/ed.txt


然后我们对这些文件进行sha1sum


h0mbre@ubuntu:~/blogpost$ sha1sum /tmp/fuzzme_original.txt /tmp/harness.txt /tmp/ed.txt 
938518c86301ab00ddf6a3ef528d7610fa3fd05a /tmp/fuzzme_original.txt
add4e6c3c298733f48fbfe143caee79445c2f196 /tmp/harness.txt
10454308b672022b40f6ce5e32a6217612b462c8 /tmp/ed.txt


我们实际上得到了三个不同的哈希值,我们希望HOOK和/bin/ed输出相同的结果,因为/bin/ed是我们加载到内存中的输入。


h0mbre@ubuntu:~/blogpost$ ls -laht /tmp
total 14M
drwxrwxrwt 28 root root 128K Apr 3 08:44 .
-rw-rw-r-- 1 h0mbre h0mbre 736K Apr 3 08:43 ed.txt
-rw-rw-r-- 1 h0mbre h0mbre 736K Apr 3 08:43 harness.txt
-rw-rw-r-- 1 h0mbre h0mbre 2.2M Apr 3 08:42 fuzzme_original.txt


啊,它们的长度至少是一样的,这意味着一定存在一些细微的差异,diff命令显示了哈希值不同的原因:


h0mbre@ubuntu:~/blogpost$ diff /tmp/ed.txt /tmp/harness.txt 
2c2
< /bin/ed: file format elf64-x86-64
---
> fuzzme: file format elf64-x86-64


argv[]数组中的文件名不同,这是唯一的区别。最终,我们能够向objdump提供一个输入文件,但实际上它从我们HOOK中的内存缓冲区获取输入。


还有一件事情,我们实际上忘记了objdump关闭了我们的文件,不是吗!于是我添加了一个快速的fclose()HOOK。如果fclose()只是想释放与fmemopen()返回的FILE *关联的堆内存,我们不会有任何问题;然而,它可能还会尝试调用close()关闭那个奇怪的文件描述符,而我们不希望这样做。最终这可能都无关紧要,但为了安全起见还是加上了。读者可以自行实验,看看会产生什么变化。假想的模糊测试器应该会在其快照恢复例程中恢复FILE *的堆内存。


结论

有无数种方法可以实现这个目标,我只是想带你们走一遍我的思考过程。实际上,有很多很酷的事情你可以用这个HOOK做,其中一件事是HOOKmalloc(),让它在大规模分配时失败,这样我就不会浪费模糊测试周期在最终会超时的事情上。你还可以创建一个at_exit()卡点,这样无论如何,程序每次退出时都会执行你的at_exit()函数,这对于快照重置很有用,尤其是在程序可能有多个退出路径的情况下,因为你只需要覆盖一个退出点。


希望这对某些人有帮助!HOOK的完整代码在下面,祝模糊测试顺利!


/* 
Compiler flags:
gcc -shared -Wall -Werror -fPIC blog_harness.c -o blog_harness.so -ldl
*/


#define _GNU_SOURCE /* dlsym */
#include /* printf */
#include /* stat */
#include /* exit */
#include /* __xstat, __fxstat */
#include /* dlsym and friends */
#include /* mmap */
#include /* memset */
#include /* 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;

// FILE * returned to callers of fopen64()
FILE *faked_fp = NULL;

// 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;

// Declare prototype for the real fopen and its friend fopen64
typedef FILE* (*fopen_t)(const char* pathname, const char* mode);
fopen_t real_fopen = NULL;

typedef FILE* (*fopen64_t)(const char* pathname, const char* mode);
fopen64_t real_fopen64 = NULL;

// Declare prototype for the real __fxstat
typedef int (*__fxstat_t)(int __ver, int __filedesc, struct stat *__stat_buf);
__fxstat_t real_fxstat = NULL;

// Declare prototype for the real __fcntl
typedef int (*fcntl_t)(int fildes, int cmd, ...);
fcntl_t real_fcntl = 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;
}

// Exploratory hooks to see if we're using fopen() related functions to open
// our input file
FILE* fopen(const char* pathname, const char* mode) {
printf("** fopen() called for '%s'\n", pathname);
exit(0);
}

// Our fopen hook, return a FILE* to the caller, also, if we are opening our
// target make sure we're not able to write to the file
FILE* fopen64(const char* pathname, const char* mode) {
// Resolve symbol on demand and only once
if (NULL == real_fopen64) {
real_fopen64 = _resolve_symbol("fopen64");
}

// Check to see what file we're opening
FILE* ret = NULL;
if (!strcmp(FUZZ_TARGET, pathname)) {
// We're trying to open our file, make sure it's a read-only mode
if (strcmp(mode, "r")) {
printf("** Attempt to open fuzz-target in illegal mode: '%s'\n", mode);
exit(-1);
}

// Open shared memory FILE* and return to caller
ret = fmemopen((void*)INPUT_ADDR, *(size_t*)INPUT_SZ_ADDR, mode);

// Make sure we've never fopen()'d our fuzzing target before
if (faked_fp) {
printf("** Attempting to fopen64() fuzzing target more than once\n");
exit(-1);
}

// Update faked_fp
faked_fp = ret;

// Change the filedes to something we know
ret->_fileno = 1337;
}

// We're not opening our file, send to regular fopen
else {
ret = real_fopen64(pathname, mode);
}

// Return FILE stream ptr to caller
return ret;
}

// Hook for __fxstat
int __fxstat (int __ver, int __filedesc, struct stat *__stat_buf) {
// Resolve the real fxstat
if (NULL == real_fxstat) {
real_fxstat = _resolve_symbol("__fxstat");
}

int ret = -1;

// Check to see if we're stat'ing our fuzz target
if (1337 == __filedesc) {
// Patch the global struct with current input size
st.st_size = *(size_t*)INPUT_SZ_ADDR;

// Copy global stat struct back to caller
memcpy(__stat_buf, &st, sizeof(struct stat));
ret = 0;
}

// Normal stat, send to real fxstat
else {
ret = real_fxstat(__ver, __filedesc, __stat_buf);
}

return ret;
}

// Hook for fcntl
int fcntl(int fildes, int cmd, ...) {
// Resolve fcntl symbol if needed
if (NULL == real_fcntl) {
real_fcntl = _resolve_symbol("fcntl");
}

if (fildes == 1337) {
return O_RDONLY;
}

else {
printf("** fcntl() called for real file descriptor\n");
exit(0);
}
}

// 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("** Err 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
}


译者言

本文使用chatGPT-4o翻译而成,如有错误之处,请斧正
原文链接:https://h0mbre.github.io/Fuzzing-Like-A-Caveman-6/

 end 

 精品活动推荐 

 专业社群 

部分入群专家来自:

新势力车企:

特斯拉、合众新能源-哪吒、理想、极氪、小米、宾理汽车、极越、零跑汽车、阿维塔汽车、智己汽车、小鹏、岚图汽车、蔚来汽车、吉祥汽车、赛力斯......

外资传统主流车企代表:

大众中国、大众酷翼、奥迪汽车、宝马、福特、戴姆勒-奔驰、通用、保时捷、沃尔沃、现代汽车、日产汽车、捷豹路虎、斯堪尼亚......

内资传统主流车企:

吉利汽车、上汽乘用车、长城汽车、上汽大众、长安汽车、北京汽车、东风汽车、广汽、比亚迪、一汽集团、一汽解放、东风商用、上汽商用......

全球领先一级供应商:

博世、大陆集团、联合汽车电子、安波福、采埃孚、科世达、舍弗勒、霍尼韦尔、大疆、日立、哈曼、华为、百度、联想、联发科、普瑞均胜、德赛西威、蜂巢转向、均联智行、武汉光庭、星纪魅族、中车集团、赢彻科技、潍柴集团、地平线、紫光同芯、字节跳动、......

二级供应商(500+以上):

Upstream、ETAS、Synopsys、NXP、TUV、上海软件中心、Deloitte、奇安信、为辰信安、云驰未来、信大捷安、信长城、泽鹿安全、纽创信安、复旦微电子、天融信、奇虎360、中汽中心、中国汽研、上海汽检、软安科技、浙江大学......

人员占比


公司类型占比


更多文章

不要错过哦,这可能是汽车网络安全产业最大的专属社区!

关于涉嫌仿冒AutoSec会议品牌的律师声明

一文带你了解智能汽车车载网络通信安全架构

网络安全:TARA方法、工具与案例

汽车数据安全合规重点分析

浅析汽车芯片信息安全之安全启动

域集中式架构的汽车车载通信安全方案探究

系统安全架构之车辆网络安全架构

车联网中的隐私保护问题

智能网联汽车网络安全技术研究

AUTOSAR 信息安全框架和关键技术分析

AUTOSAR 信息安全机制有哪些?

信息安全的底层机制

汽车网络安全

Autosar硬件安全模块HSM的使用

首发!小米雷军两会上就汽车数据安全问题建言:关于构建完善汽车数据安全管理体系的建议

谈思实验室 深入专注智能汽车网络安全与数据安全技术,专属汽车网络安全圈的头部学习交流平台和社区。平台定期会通过线上线下等形式进行一手干货内容输出,并依托丰富产业及专家资源,深化上下游供需对接,逐步壮大我国汽车安全文化及产业生态圈。
评论
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 47浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 66浏览
  • 艾迈斯欧司朗全新“样片申请”小程序,逾160种LED、传感器、多芯片组合等产品样片一触即达。轻松3步完成申请,境内免费包邮到家!本期热荐性能显著提升的OSLON® Optimal,GF CSSRML.24ams OSRAM 基于最新芯片技术推出全新LED产品OSLON® Optimal系列,实现了显著的性能升级。该系列提供五种不同颜色的光源选项,包括Hyper Red(660 nm,PDN)、Red(640 nm)、Deep Blue(450 nm,PDN)、Far Red(730 nm)及Ho
    艾迈斯欧司朗 2024-11-29 16:55 150浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-29 14:30 116浏览
  • 国产光耦合器因其在电子系统中的重要作用而受到认可,可提供可靠的电气隔离并保护敏感电路免受高压干扰。然而,随着行业向5G和高频数据传输等高速应用迈进,对其性能和寿命的担忧已成为焦点。本文深入探讨了国产光耦合器在高频环境中面临的挑战,并探索了克服这些限制的创新方法。高频性能:一个持续关注的问题信号传输中的挑战国产光耦合器传统上利用LED和光电晶体管进行信号隔离。虽然这些组件对于标准应用有效,但在高频下面临挑战。随着工作频率的增加,信号延迟和数据保真度降低很常见,限制了它们在电信和高速计算等领域的有效
    腾恩科技-彭工 2024-11-29 16:11 105浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 54浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 58浏览
  • 光耦合器作为关键技术组件,在确保安全性、可靠性和效率方面发挥着不可或缺的作用。无论是混合动力和电动汽车(HEV),还是军事和航空航天系统,它们都以卓越的性能支持高要求的应用环境,成为现代复杂系统中的隐形功臣。在迈向更环保技术和先进系统的过程中,光耦合器的重要性愈加凸显。1.混合动力和电动汽车中的光耦合器电池管理:保护动力源在电动汽车中,电池管理系统(BMS)是最佳充电、放电和性能监控背后的大脑。光耦合器在这里充当守门人,将高压电池组与敏感的低压电路隔离开来。这不仅可以防止潜在的损坏,还可以提高乘
    腾恩科技-彭工 2024-11-29 16:12 117浏览
  • By Toradex胡珊逢简介嵌入式领域的部分应用对安全、可靠、实时性有切实的需求,在诸多实现该需求的方案中,QNX 是经行业验证的选择。在 QNX SDP 8.0 上 BlackBerry 推出了 QNX Everywhere 项目,个人用户可以出于非商业目的免费使用 QNX 操作系统。得益于 Toradex 和 QNX 的良好合作伙伴关系,用户能够在 Apalis iMX8QM 和 Verdin iMX8MP 模块上轻松测试和评估 QNX 8 系统。下面将基于 Apalis iMX8QM 介
    hai.qin_651820742 2024-11-29 15:29 147浏览
  • 国产光耦合器正以其创新性和多样性引领行业发展。凭借强大的研发能力,国内制造商推出了适应汽车、电信等领域独特需求的专业化光耦合器,为各行业的技术进步提供了重要支持。本文将重点探讨国产光耦合器的技术创新与产品多样性,以及它们在推动产业升级中的重要作用。国产光耦合器创新的作用满足现代需求的创新模式新设计正在满足不断变化的市场需求。例如,高速光耦合器满足了电信和数据处理系统中快速信号传输的需求。同时,栅极驱动光耦合器支持电动汽车(EV)和工业电机驱动器等大功率应用中的精确高效控制。先进材料和设计将碳化硅
    克里雅半导体科技 2024-11-29 16:18 149浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 53浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 80浏览
  • 随着航空航天技术的迅猛发展,航空电子网络面临着诸多挑战,如多网络并行传输、高带宽需求以及保障数据传输的确定性等。为应对这些挑战,航空电子网络急需一个通用的网络架构,满足布线简单、供应商多、组网成本相对较低等要求。而以太网技术,特别是TSN(时间敏感网络)的出现,为航空电子网络带来了新的解决方案。本文将重点介绍TSN流识别技术在航空电子网络中的应用,以及如何通过适应航空电子网络的TSN流识别技术实现高效的航空电子网络传输。一、航空电子网络面临的挑战航空航天业专用协议包括AFDX、ARINC等,这些
    虹科工业智能互联 2024-11-29 14:18 100浏览
  • 在现代科技浪潮中,精准定位技术已成为推动众多关键领域前进的核心力量。虹科PCAN-GPS FD 作为一款多功能可编程传感器模块,专为精确捕捉位置和方向而设计。该模块集成了先进的卫星接收器、磁场传感器、加速计和陀螺仪,能够通过 CAN/CAN FD 总线实时传输采样数据,并具备内部存储卡记录功能。本篇文章带你深入虹科PCAN-GPS FD的技术亮点、多场景应用实例,并展示其如何与PCAN-Explorer6软件结合,实现数据解析与可视化。虹科PCAN-GPS FD虹科PCAN-GPS FD的数据处
    虹科汽车智能互联 2024-11-29 14:35 147浏览
  • 在电子技术快速发展的今天,KLV15002光耦固态继电器以高性能和强可靠性完美解决行业需求。该光继电器旨在提供无与伦比的电气隔离和无缝切换,是现代系统的终极选择。无论是在电信、工业自动化还是测试环境中,KLV15002光耦合器固态继电器都完美融合了效率和耐用性,可满足当今苛刻的应用需求。为什么选择KLV15002光耦合器固态继电器?不妥协的电压隔离从本质上讲,KLV15002优先考虑安全性。输入到输出隔离达到3750Vrms(后缀为V的型号为5000Vrms),确保即使在高压情况下,敏感的低功耗
    克里雅半导体科技 2024-11-29 16:15 118浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦