基于套接字的模糊测试技术之Apache HTTP(中):自定义拦截器

阅读量    126063 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

​ 在本系列第一部分中,我解释了如何开始对 Apache HTTP 进行模糊测试,如何在 AFL++ 中实现自定义突变器,以及如何定义您自己的 HTTP 语法

​ 在第二部分中,我将重点介绍如何构建我们自己的自定义 ASAN 拦截器,以便在实现自定义内存池时捕获内存错误,以及如何拦截文件系统系统调用以检测目标应用程序中的逻辑错误

​ 让我们继续吧!

 

一、人工中毒

​ 让我们首先快速回顾一下 Address Sanitizer (ASAN) 影子内存和中毒是如何工作的

​ ASAN维护了一个影子内存,用于跟踪实际内存中的每个字节,并可以确定内存中的任何给定字节是否可地址访问。无效内存区域中的字节称为红色区域或中毒内存

​ 因此,当您使用 Address Sanitizer 编译您的程序时,它会检测每个内存访问并为其添加一个检查前缀。然后,ASAN 将跟踪程序,如果程序尝试写入无效的内存区域,ASAN 将停止执行并生成诊断报告。否则,它将允许程序继续运行。这允许您检测各种无效的内存访问和内存管理不善等问题

​ 在某些情况下,对中毒内存有更自由的控制权限对开发人员和安全研究人员很有用。例如,在一个自定义函数中,您以一种ASAN无法捕获的方式处理内存。这就是为什么 ASAN 还提供了手动内存中毒外部 API,允许用户手动对内存区域进行中毒和解除中毒。

​ 我们可以通过在程序里导入包含了这些外部 ASAN 接口的ASAN 库来使用这些功能:

#include <sanitizer/asan_interface.h>

​ 然后我们可以在分别调用mallocfree时用ASAN_POISON_MEMORY_REGIONASAN_UNPOISON_MEMORY_REGION宏。典型的工作流程是首先毒害整个内存区域,然后解除分配的内存块毒害,在它们之间留下毒害的红色区域。

​ 这种方法相对简单,易于实现,但每当我们针对一个新程序时,都会遇到“重新实现轮子”的问题。如果我们能像ASAN那样,简单地拦截某一组功能,那就太好了。

​ 出于这个原因,我将展示另一种方法:自定义拦截器

 

二、自定义拦截器

2.1 动机

​ 在这篇博文的开头,我谈到了需要实现自定义拦截器来处理自定义内存池实现,就像Apache HTTP的情况一样。所以我们要问自己的问题是“为什么我们需要实现自定义拦截器?”

​ 让我们看一个例子来更好地理解

​ 例如,考虑以下代码片段,其中调用了apr_palloc以分配内存:

​ 在这种情况下,第二个参数的值为 126 ( in_size = 126) ,换句话说,我们的目标是在g->apr_pool这个内存池中分配 126 个字节。由于内存对齐要求,这 126 个字节将向上舍入为 128 个字节。这很清楚

​ 如果您看过我们之前的ProFTPd博客文章,您可以找到有关内部如何实现 ProFTPd 内存池的知识。这个内存池实现是基于 Apache HTTP 的,所以在这种情况下,两者的实现是几乎相同的。Apache HTTP 内存池由内存节点的链表组成,如下所示:

​ 然后,程序将在需要额外空间时向此链表添加新节点。当一个节点的空闲空间不足以满足apr_palloc需求时,则调用allocator_alloc函数。该函数将负责创建一个新节点并将其添加到链表中。然而,正如我们在下图中看到的,这样的分配大小总是向上舍入到MIN_ALLOC字节。因此,这些节点中的每一个的最小大小为MIN_ALLOC

​ 稍后,在这个函数中调用malloc,目的是为这个节点分配新的内存。在下图中,您可以观察到size=8192malloc调用是如何执行的:

​ 我们发现自己面临这样一个场景:我们以 size = 126调用了apr_palloc

​ ……但是 ASAN 已经毒害了一个大小为 8192 的内存区域:

​ 最终的结果是总共8192-126 = 8066字节被ASAN标记为可写,而实际上它不是真正分配的内存,而是节点中的空闲空间。因此,一个后续的memcpy(np, source, 5000)调用将导致一个超出范围的写操作,覆盖节点的其余内存。然而,我们不会看到任何ASAN警报消息,这将导致我们错过内存损坏错误,即使我们