文章总结: 本文以FreeType为例,详解了为库编写高效FuzzingHarness的方法。内容涵盖AFL++插桩编译、研读文档理解内部机制、识别关键API及编写测试代码。文章强调深入分析内存管理与I/O流对提升覆盖率的重要性,提供了从基础构建到深度挖掘的实战指南,帮助安全研究人员高效发现库漏洞。 综合评分: 90 文章分类: 安全工具,漏洞分析,安全开发,实战经验
Fuzzing 的艺术:如何为库编写高效的 Harness
Bushido Security Bushido Security
securitainment
2026年3月2日 10:24 日本
| 原文链接 | 作者 | | — | — | | https://bushido-sec.com/index.php/2025/01/03/fuzzing-harness-guide/ | Bushido Security |
Intro
每位安全研究人员或 Fuzzing 爱好者都梦寐以求一个理想目标:接受文件输入、覆盖率深、执行速度极快。遗憾的是,现实中只有极少数目标能满足这些条件,因此对它们进行 “盲目 Fuzzing” 并不可取 (你确实不该这么做!请参考 这篇指南)。
大多数目标并非开箱即用地适合 Fuzzing,需要研究人员投入大量精力来构建高效的 Fuzzing 环境。本文将探讨 _如何对库进行 Fuzzing_,涵盖从 基础知识到 持久模式的完整流程。我们以 Freetype 为例——一个广泛使用的字体文件内容访问库。
对库进行 Fuzzing 可以归纳为以下关键步骤:
- 对库进行插桩
- 研究文档
- 识别感兴趣的函数
- 编写 harness
- 编写特定 harness
对库进行插桩
插桩是指修改程序的二进制文件或源代码,植入跟踪和监控机制。这些机制帮助 Fuzzer 收集有价值的覆盖率信息,从而引导 Fuzzing 过程。借助插桩,AFL++ 能够识别 Fuzzing 过程中执行了哪些代码路径,实现更智能、更高效的路径探索。AFL++ 提供多种插桩方式,其中 afl-clang-fast可以兼容 hongfuzz 和 libfuzzer,同时比 afl-clang-lto更易于调试。
安装 AFL++
安装说明可在 此处 查看。首先安装 LLVM
# Install a specific version of LLVM:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh # <version number>
然后可以继续安装 AFL++
sudo apt-get install -y build-essential python3-dev automake cmake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools cargo libgtk-3-dev
sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-dev
sudo apt-get install -y ninja-build # for QEMU mode
sudo apt-get install -y cpio libcapstone-dev # for Nyx mode
sudo apt-get install -y wget curl # for Frida mode
sudo apt-get install -y python3-pip # for Unicorn mode
git clone https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
make
sudo make install
编译目标库
编译库的过程可能因目标不同而复杂程度各异。在本练习中,Freetype 的编译过程非常简单。
git clone https://gitlab.freedesktop.org/freetype/freetype.git
cd freetype
./autogen.sh # Generates the configure file
./configure CC=afl-clang-fast CXX=afl-clang-fast++ CFLAGS="-O1" CXXFLAGS="-O1"
make
如果一切顺利,你将得到多个 .a文件,它们共同组成该库。
测试库
为了验证一切是否正确,我们来编写一个简单的 harness。首先查阅 Freetype 文档 页面,找到一个 教程,其中描述了使用该库的基本步骤。按照此指南,我们将编写这个极简的 harness。
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
int main(int argc, char ** argv)
{
FT_Library library; /* handle to library */
FT_Face face; /* handle to face object */
FT_Error error; /* hande to error*/
error = FT_Init_FreeType(&library);
if (error) { printf("Could not load the library."); }
char * filename = argv[1];
error = FT_New_Face(library, filename, 0, &face); /* create face object */
if (error) { printf("Could not create a face."); return 1; }
// Cleanup
FT_Done_Face(face);
FT_Done_FreeType(library);
return 0;
}
现在可以用以下命令编译我们的目标:
afl-clang-fast simple_harness.c -I/path/to/include -L/path/to/freetype -lfreetype -o test
如果一切顺利,你将得到一个可以进行 Fuzzing 的 test``.elf文件。可以运行以下命令尝试 Fuzzing:afl-fuzz -i inputs -o output -- ./test @@
恭喜,你已经成功为一个库编写了 harness!不过,这个程序做的事情不多,对吧?实际上,我们写的代码不太可能发现新的 bug。Freetype 作为一个经过广泛测试的库,在这样基础的函数中不太可能存在内存问题 (除非……?)。现在是时候编写更高级的 harness 了——一个真正有可能发现新 bug 的 harness!
阅读文档
阅读库或软件的文档往往令人感到枯燥,因此经常被忽略。毕竟:花几天时间调试就能省去阅读几页文档的麻烦,对吧?玩笑归玩笑,如果你打算对特定的库或程序进行 Fuzzing,就必须愿意花时间去了解它。在当今时代,不投入时间和精力去理解目标,而盲目地用 Fuzzer 对任意目标进行测试的策略已被证明效率极低。
在为库编写 harness 时,理解该库 做什么至关重要。问问自己:
- 它的主要用途是什么?
- 它的主要功能有哪些?
- 它如何处理输入?
- 库的内部机制是什么?
- 哪些功能处理用户输入?
这一阶段的目标是初步了解库——它的主要组件、功能以及值得 Fuzzing 的领域。在 Freetype 的主要 文档 中,我们找到了 FAQ 部分,其中包含指向 什么是 Freetype? 页面的链接。
什么是 FreeType?
Freetype 是:
“它是一个软件库,可供各种应用程序用来访问字体文件的内容。最值得注意的是,它支持以下功能。
- 它提供统一的接口来访问字体文件。支持位图和可缩放格式,包括 TrueType、OpenType、Type1、CID、CFF、Windows FON/FNT、X11 PCF 等。
- 它支持高速、抗锯齿的字形位图生成,具有 256 级灰度。
- 它具有极高的模块化程度,每种字体格式由特定模块支持。库的构建可以定制为仅支持所需格式,从而减小代码体积。FreeType 的最小抗锯齿构建可以小于 30kByte。”
文档还描述了 FreeType 不做哪些事情:
FreeType 并不试图实现各种复杂功能,因为它专注于提供卓越的字体服务。这意味着以下功能不由该库直接支持:
-
将字形渲染到任意表面
-
字形缓存
-
文本布局
以上内容让我们对 FreeType 的功能有了基本认识。接下来,让我们深入文档,阅读 设计 部分。
FreeType 设计
文档将 FreeType 描述为一个 _组件集合_,其中每个组件负责一项特定任务。
- 客户端应用程序通常调用 FreeType 2 的 高级 API,其函数在一个称为 Base Layer 的单一组件中实现。
- 根据上下文或任务,Base Layer 会调用一个或多个模块组件来执行工作。在大多数情况下,客户端应用程序不需要知道调用了哪个模块。
- Base Layer 还包含一组用于通用操作的例程,如内存分配、列表处理、I/O 流解析、定点计算等。这些函数也可以在任何时候被模块调用,它们构成了所谓的 低级基础 API。
内部对象和类
该 章节 介绍了 FreeType 的内存管理和输入流基本机制。以下是从文档中提取的几条关键信息:
-
大部分内存管理操作通过基础层的三个专用例程完成:FT_Alloc、FT_Realloc 和 FT_Free。每个函数都要求以 FT_Memory 句柄作为第一个参数。需要注意的是,还有更多用于特定目的的类似变体,此处为简洁起见不再赘述。默认情况下,该管理器使用 ANSI 标准函数 malloc、realloc 和 free。不过,由于 ftsystem 是基础层中可替换的部分,特定的库构建版本可以提供不同的默认内存管理器。
-
字体文件始终通过 FT_Stream 对象进行读取。
FT_StreamRec的定义位于公共头文件 ftsystem.h 中,这使得客户端开发者可以按需提供自己的流实现。函数FT_New_Face始终会根据第二个参数传入的 C 路径名自动创建新的流对象,具体是通过调用 ftsystem 组件提供的内部函数 FT_Stream_Open 来实现的。由于该组件可被替换,流的实现方式在不同平台间可能存在很大差异。举例来说,默认的流实现位于 src/base/ftsystem.c 文件中,使用 ANSI 标准函数 fopen、fseek 和 fread。然而,FreeType 2 的 Unix 构建版本提供了一种替代实现——在宿主平台支持的情况下使用内存映射文件,从而显著提升访问速度。
总结
综上所述,我们了解到 FreeType 具有以下特点:
-
提供对字体类型的访问能力。
-
采用模块化架构。
-
依赖低级 API 进行 I/O 管理。
-
大部分内存管理通过专用例程完成。
-
通过
FT_Stream对象读取字体文件。 -
Unix 构建版本提供了支持内存映射文件的实现。
文档还指出了在使用或测试该库时值得重点关注的几个方面:
-
内存管理系统的集中化特性使其成为可靠性测试的关键环节。
-
流抽象层 (特别是 Unix 构建中使用内存映射文件的部分) 是一个复杂的交互节点。
-
模块化架构意味着测试应同时覆盖单个模块内部和模块之间的交互。
幸运的是,编写优秀的 harness 并不要求你成为 FreeType 专家。凭借对其内部机制的扎实理解,我们现在已经有能力在 harness 中高效地实现库函数。
列出感兴趣的函数
现在我们已经充分理解了该库的功能,接下来要列出值得进行 Fuzz 测试或对编写 harness 至关重要的函数。我们从 FreeType 教程 页面入手,收集了以下函数:
教程 1
- 库初始化
- 初始化库:
FT_Init_FreeType(&library) - 加载字体面 (Face)
- 从文件加载字体面:
FT_New_Face(library, "/usr/share/fonts/truetype/arial.ttf", 0, &face) - 从内存加载字体面:
FT_New_Memory_Face(library, buffer, size, 0, &face) - 从其他来源加载:
FT_Open_Face(library, args, face_index, *aface) - 设置当前像素大小
- 设置字符大小:
FT_Set_Char_Size(face, 0, 16*64, 300, 300) - 设置像素大小:`FT_Set_Pixel_Sizes(face, 0, 16 )
- 加载字形图像
- 将 Unicode 字符码转换为字形索引:
glyph_index = FT_Get_Char_Index(face, charcode) - 从字体面加载字形:
FT_Load_Glyph(face, glyph_index, load_flags) - 将字形转换为位图:
FT_Render_Glyph(face->glyph, render_mode) - 使用其他字符映射表:
FT_Select_Charmap(face, FT_ENCODING_BIG5) - 字形变换
- 设置变换矩阵:
FT_Set_Transform(face, &matrix, &delta)
教程 2
- 字形管理:
- 提取字形图像:
FT_Get_Glyph(face->glyph, &glyph) - 对字形图像进行变换:
FT_Glyph_Transform(glyph, 0, &delta) - 复制字形图像:
FT_Glyph_Copy(glyph, &glyph2) - 测量字形图像:
FT_Glyph_Get_Cbox(glyph, _bbox_mode_, &bbox) - 将字形图像转换为位图:
FT_Glyph_To_Bitmap(&glyph, render_mode, &origin, 1) - 全局字形度量:
- 通过文件加载附加度量信息:
FT_Attach_File - 通过流加载附加度量信息:
FT_Attach_Stream - 获取字距调整信息:
FT_Get_Kerning(face, left, right, kerning_mode, &kerning)
幸运的是,一些库提供了示例代码 (有时相当高级),有助于识别感兴趣的函数并理解其用途。在我们的案例中,FreeType 提供了一个非常实用的文件夹 ft2demos,其中包含大量完整的库使用示例。本文将利用这些示例帮助我们更好地理解函数用法,你也可以借助它们来整理函数列表。
现在我们有了这份列表,它为后续工作提供了一个坚实的起点。不过,它并未覆盖整个库。为了实现尽可能深的代码覆盖率,我们还需要深入研究该库的 API 文档。虽然这部分工作可能比较耗时,但为了打造一个能够发现 bug 的强大 harness,这些付出是完全值得的。
API
另一个关于库函数的优秀信息来源是 API 文档。理想情况下,所有函数都应有文档记录,这使你能够手动编写全面有效的 harness。
核心 API
-
字体面创建
-
FT_New_Face:调用 FT_Open_Face,通过路径名打开字体。
-
FT_Done_Face:销毁指定的字体面对象及其所有子槽 (slot) 和大小 (size) 对象。
-
FT_Reference_Face:FT_Face 结构创建时,引用计数器被初始化为 1。此函数将计数器递增。FT_Done_Face 仅在计数器为 1 时销毁字体面,否则只是递减计数器。
-
FT_New_Memory_Face:调用 FT_Open_Face,打开已加载到内存中的字体。
-
FT_Face_Properties:在逐字体面的基础上设置或覆盖某些 (库级或模块级的) 属性。适用于更精细的控制,并避免对共享结构加锁 (线程可以随意修改自己的字体面)。
-
FT_Open_Face:根据 FT_Open_Args 描述的资源创建字体面对象。
-
FT_Attach_File:调用 FT_Attach_Stream 来附加文件。
-
FT_Attach_Stream:向字体面对象”附加”数据。通常用于读取字体面对象的附加信息。例如,可以附加随 Type 1 字体提供的 AFM 文件来获取字距调整值和其他度量信息。
-
字体测试宏
-
FT_HAS_HORIZONTAL:检查是否有水平度量信息
-
FT_HAS_VERTICAL:检查是否有垂直度量信息
-
FT_HAS_KERNING:检查字体面是否包含可通过
FT_Get_Kerning访问的字距调整数据 -
FT_HAS_FIXED_SIZES:检查字体面是否包含嵌入位图
-
FT_HAS_GLYPH_NAMES:检查字体面是否包含部分字形名称
-
FT_HAS_COLOR:检查字体面是否包含彩色字形表
-
FT_HAS_MULTIPLE_MASTERS:检查字体面是否包含多主字体 (Multiple Masters)
-
FT_HAS_SVG:检查字体面是否包含 SVG OpenType 表
-
FT_HAS_SBIX:检查字体面是否包含 sbix OpenType 表和轮廓字形
-
FT_HAS_SBIX_OVERLAY:检查字体面是否包含 sbix OpenType 表且其 flags 字段的 bit 1 已设置
-
FT_IS_SFNT:检查字体面是否包含基于 SFNT 存储方案的字体格式
-
FT_IS_SCALABLE:检查字体面是否包含可缩放字体
-
FT_IS_FIXED_WITH:检查字体面是否包含等宽字体
-
FT_IS_CID_KEYED:检查字体面是否为 CID-keyed 字体
-
FT_IS_TRICKY:检查字体面是否为 tricky 字体
-
FT_IS_NAMED_INSTANCE:检查字体面是否为 GX 或 OpenType 可变字体的命名实例
-
FT_IS_VARIATION:检查字体面是否已被 FT_Set_MM_Design_Coordinates、FT_Set_Var_Design_Coordinates、FT_Set_Var_Blend_Coordinates 或 FT_Set_MM_WeightVector 修改
-
尺寸与缩放
-
FT_Set_Char_Size:调用 FT_Request_Size 请求标称大小
-
FT_Set_Pixel_Sizes:调用 FT_Request_Size 请求标称大小 (以像素为单位)
-
FT_Request_Size:调整字体面中活动 FT_Size 对象的缩放比例
-
FT_Select_Size:选择一个位图规格,设置字体面中活动 FT_Size 对象的缩放因子
-
FT_Set_Transform:设置通过 FT_Load_Glyph 加载字形图像到字形槽时应用的变换
-
FT_Get_Transform:返回通过 FT_Load_Glyph 加载字形图像到字形槽时应用的变换
-
字形检索
-
FT_Load_Glyph:将字形加载到字体面对象的字形槽中
-
FT_Render_Glyph:将给定的字形图像转换为位图
-
FT_Get_Kerning:返回同一字体面中两个字形之间的字距调整向量
-
FT_Get_Track_Kerning:返回给定字体面对象在指定大小下的 track 字距调整值
-
字符映射
-
FT_Select_Charmap:通过编码选择指定的字符映射表
-
FT_Set_Charmap:为字符码到字形索引的映射选择指定的字符映射表
-
FT_Get_Charmap_Index:获取指定字符映射表的索引
-
FT_Get_Char_Index:返回给定字符码的字形索引
-
FT_Get_First_Char:返回当前字符映射表中的第一个字符码
-
FT_Get_Next_Char:返回当前字符映射表中的下一个字符码
-
FT_Load_Char:通过字符码将字形加载到字体面对象的字形槽中
-
信息检索
-
FT_Get_Name_Index:返回给定字形名称的字形索引
-
FT_Get_Glyph_Name:获取给定字形的 ASCII 名称
-
FT_Get_Postscript_Name:获取给定字体面的 ASCII PostScript 名称
-
FT_Get_FSType_Flags:返回字体的 fsType 标志
-
FT_Get_SubGlyph_Info:获取给定子字形的描述信息
扩展 API
-
Unicode 变体序列
-
FT_Face_GetCharVariantIndex:返回经变体选择器修改后的给定字符码的字形索引
-
FT_Face_GetCharVariantIsDefault:检查该 Unicode 字符的此变体是否为字符映射表中的默认变体
-
FT_Face_GetVariantSelectors:返回字体中找到的以零结尾的 Unicode 变体选择器列表
-
FT_Face_GetVariantsOfChar:返回为指定字符码找到的以零结尾的 Unicode 变体选择器列表
-
FT_Face_GetCharsOfVariant:返回为指定变体选择器找到的以零结尾的 Unicode 字符码列表
-
字形颜色管理
-
FT_Palette_Data_Get:获取字体面的调色板数据
-
FT_Palette_Select:该函数有两个用途:激活用于渲染的调色板 / 获取该调色板的所有 (未修改的) 颜色条目。函数返回一个可读写数组,这意味着调用方应用程序可以按需修改调色板条目。
-
FT_Palette_Set_Foreground_Color:
COLR使用调色板索引 0xFFF 表示文本前景色。此函数用于设置该值。 -
字形图层管理
-
FT_Get_Color_Glyph_Layer:这是 OpenType 字体中 ‘COLR’ v1 表的接口,用于迭代获取与当前字形槽关联的彩色字形图层
-
FT_Get_Color_Glyph_Paint:作为 OpenType 字体中 ‘COLR’ v1 表颜色渐变信息的起始点和接口,用于获取有向无环图的绘制表
-
FT_Get_Color_Glyph_ClipBox:搜索指定 base_glyph 的 ‘COLR’ v1 裁剪框,并将裁剪框信息填充到 clip_box 参数中
-
FT_Get_Paint_Layer:访问
PaintColrLayers表的图层 -
FT_Get_Colorline_Stops:这是 ‘COLR’ v1 表中颜色渐变信息的接口,用于获取渐变和纯色填充信息
-
FT_Get_Paint:使用
FT_OpaquePaint对象访问绘制的详细信息 -
字形管理
-
FT_New_Glyph:创建一个新的空字形图像
-
FT_Get_Glyph:从字形槽中提取字形图像
-
FT_Glyph_Copy:复制字形图像
-
FT_Glyph_Transform:如果字形图像格式可缩放,则对其进行变换
-
FT_Glyph_Get_CBox:返回字形控制框
-
FT_Glyph_To_Bitmap:将给定的字形对象转换为位图字形对象
-
FT_Done_Glyph:销毁给定的字形
字体操作的广泛 API 接口为发现 bug 和潜在安全漏洞提供了多个攻击向量。FT_Face_Properties和 FT_Attach_Stream等函数允许动态修改字体面对象和附加外部数据,如果输入验证不充分,可能暴露内存损坏或缓冲区溢出漏洞。变换函数 (FT_Set_Transform、FT_Glyph_Transform) 引入了复杂的数学运算,可能揭示数值精度问题或坐标计算中的边界情况。颜色管理函数 (FT_Palette_Select、FT_Get_Color_Glyph_Paint) 通过处理颜色表和渐变增加了额外的复杂度,在处理畸形颜色数据时可能暴露解析 bug 或内存问题。字形管理函数,尤其是 FT_Glyph_To_Bitmap和 FT_Get_Color_Glyph_Layer,涉及格式转换和图层处理,可能暴露内存管理问题或渲染管线中的 bug。彻底测试这些函数至关重要,因为它们经常与复杂的字体数据结构交互,并执行可能在处理不当时导致崩溃或安全漏洞的内存操作。
编写 harness
Harness 教程 1
我们的第一个 harness 将使用从教程 1 中收集的函数:
FT_Init_FreetypeFT_New_FaceFT_Set_Char_SizeFT_Set_Pixel_SizesFT_Get_Char_IndexFT_Load_GlyphFT_Render_GlyphFT_Set_Transform
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include <math.h>
int main(int argc, char *argv[])
{
FT_Library library; // handle to library
FT_Face face; // handle to face object
FT_Error error; // hande to error
FT_UInt glyph_index; // Glyph index
FT_ULong charcode; // Char code
FT_Matrix matrix; // Matrix
FT_Vector delta; // Delta
FT_GlyphSlot slot = face->glyph; // a small shortcut
error = FT_Init_FreeType(&library);
if (error) { printf("Could not load the library"); }
char *filename = argv[1];
// Create a face object
error = FT_New_Face(library, filename, 0, &face);
if (error) { printf("Could not create a face"); return 0; }
// Setting the current char pixel size
error = FT_Set_Char_Size(face, 0, 16*64, 300,300);
if (error) { printf("Could not set current pixel size"); return 0; }
// Setting the pixel size
error = FT_Set_Pixel_Sizes(face, 0, 16);
if (error) { printf("Could not set current pixel size"); return 0; }
// Convert a Unicode character code to a font glyph index
glyph_index = FT_Get_Char_Index( face, charcode );
// Loading glyph from the face
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
if (error) { printf("Could not load glyph from the face"); return 0; }
// Convert to bitmap
error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
if (error) { printf("Could not convert glyph to bitmap"); return 0; }
// Setup matrix
double angle = (25.0 / 360) * 3.14159 * 2;
matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L);
matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L);
matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L);
delta.x = 300 * 64;
delta.y = (480 - 200) * 64;
// Set transform
FT_Set_Transform(face, &matrix, &delta);
// Cleanup
FT_Done_Face(face);
FT_Done_FreeType(library);
return 0;
}
尽管这个 harness 看起来很简单,但它是一个非常好的示例,展示了如何开始对库进行 Fuzzing。其中包含了一些值得 AFL 探索的函数,可能存在潜在的 bug。
Harness 教程 2
我们的第二个 harness 将使用从教程 2 中收集的函数:
FT_Init_FreetypeFT_New_FaceFT_Get_Char_IndexFT_Get_GlyphFT_Glyph_CopyFT_Glyph_TransformFT_Glyph_Get_CBoxFT_Get_Kerning
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
int main(int argc, char *argv[])
{
FT_Library library; // handle to library
FT_Face face; // handle to face object
FT_Error error; // hande to error
FT_Glyph glyph, glyph2; // Glyph and Glyph2
FT_UInt glyph_index; // Glyph index
FT_Matrix matrix; // Matrix
FT_Vector delta, origin, kerning; // Delta, Origin and Kerning
FT_BBox bbox; // Bbox
FT_ULong charcode; // Char code
FT_UInt previous; // Previous and Next
// Init library
error = FT_Init_FreeType(&library);
if (error) { printf("Could not load the library"); return 0; }
char *filename = argv[1];
// Create a face object
error = FT_New_Face(library, filename, 0, &face);
if (error) { printf("Could not create a face"); return 0; }
// Convert a Unicode character code to a font glyph index
glyph_index = FT_Get_Char_Index( face, charcode);
// Load the glyph
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
if (error) { printf("Could not load the glyph"); return 0; }
// Get the glyph
error = FT_Get_Glyph( face->glyph, &glyph );
if (error) { printf("Could not get the glyph"); return 0; }
// Copy the glyph
error = FT_Glyph_Copy( glyph, &glyph2 );
if (error) { printf("Could not copy the glyph"); return 0; }
// Translate glyph
delta.x = -100 * 64; /* coordinates are in 26.6 pixel format */
delta.y = 50 * 64;
FT_Glyph_Transform(glyph, 0, &delta);
// Transform glyph2 (horizontal shear)
matrix.xx = 0x10000L;
matrix.xy = 0.12 * 0x10000L;
matrix.yx = 0;
matrix.yy = 0x10000L;
FT_Glyph_Transform(glyph2, &matrix, 0);
// Measuring the glyph image
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_UNSCALED, &bbox);
// Converting glyph image to a bitmap
origin.x = 32; /* 1/2 pixel in 26.6 format */
origin.y = 0;
error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, &origin, 1);
if (error) { printf("Could not convert the glyph to bitmap"); return 0; }
// Retrieve kerning information
error = FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &kerning);
if (error) { printf("Could not retrieve the kerning info"); return 0; }
return 0;
}
优化 harness
我们可以通过使用 AFL++ 的 持久模式 从内存传递输入而非使用文件 I/O,从而大幅提升 harness 的执行速度。
LLVMTestOneInput
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
return 0;
}
AFL 持久模式
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
/* this lets the source compile without afl-clang-fast/lto */
#ifndef __AFL_FUZZ_TESTCASE_LEN
ssize_t fuzz_len;
unsigned char fuzz_buf[1024000];
#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
#define __AFL_FUZZ_INIT() void sync(void);
#define __AFL_LOOP(x) \
((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
#define __AFL_INIT() sync()
#endif
__AFL_FUZZ_INIT();
int main(int argc, char ** argv)
{
size_t len; /* how much input did we read? */
unsigned char *buf; /* test case buffer pointer */
buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(UINT_MAX)) {
len = __AFL_FUZZ_TESTCASE_LEN;
if (len < 8) { continue; } // Check len minimum size
}
return 0;
}
这使得我们的 harness 从 5000 次执行/秒提升到 40000 次执行/秒!
Harness API
编写 harness 有多种方式。你可以选择编写一个使用大量 (甚至所有) 函数的 大型harness,也可以将某些函数进行分组。我们将两种方式都尝试:
- 每个 API 子主题一个相对较小的 harness
- 每个 API 一个中型 harness,称为
API harness
API 子主题 harness
字体测试宏 harness
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ft2build.h>
#include <math.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < 10) return 0; // Reject very small inputs
FT_Library library; // handle to library
FT_Face face; // handle to face object
FT_Error error; // hande to error
// Init library
error = FT_Init_FreeType(&library);
if (error) { printf("Could not load the library"); return 0; }
error = FT_New_Memory_Face(library,
data, /* first byte in memory */
size, /* size in bytes */
0, /* face_index */
&face );
if (error) { printf("Could not create a face"); return 0; }
// Macros testing
FT_HAS_HORIZONTAL(face);
FT_HAS_VERTICAL(face);
FT_HAS_KERNING(face);
FT_HAS_FIXED_SIZES(face);
FT_HAS_GLYPH_NAMES(face);
FT_HAS_COLOR(face);
FT_HAS_MULTIPLE_MASTERS(face);
FT_HAS_SVG(face);
FT_HAS_SBIX(face);
FT_HAS_SBIX_OVERLAY(face);
FT_IS_SFNT(face);
FT_IS_SCALABLE(face);
FT_IS_FIXED_WIDTH(face);
FT_IS_CID_KEYED(face);
FT_IS_TRICKY(face);
FT_IS_NAMED_INSTANCE(face);
FT_IS_VARIATION(face);
// Cleanup
FT_Done_Face(face);
// Cleanup
FT_Done_FreeType(library);
return 0;
}
尺寸与缩放
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ft2build.h>
#include <math.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < 10) return 0; // Reject very small inputs
FT_Library library; // handle to library
FT_Face face; // handle to face object
FT_Error error; // hande to error
FT_Matrix matrix; // Matrix
FT_Vector delta; // Delta
// Create a size request
FT_Size_RequestRec req;
req.type = FT_SIZE_REQUEST_TYPE_NOMINAL; // Request nominal size
req.width = 0; // Width in 26.6 fractional points (0 means same as height)
req.height = 16 * 64; // Height in 26.6 fractional points (16pt)
req.horiResolution = 96; // Horizontal resolution in dpi
req.vertResolution = 96; // Vertical resolution in dpi
// Setup matrix
double angle = (25.0 / 360) * 3.14159 * 2;
matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L);
matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L);
matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L);
delta.x = 300 * 64;
delta.y = (480 - 200) * 64;
// Init library
error = FT_Init_FreeType(&library);
if (error) { printf("Could not load the library"); return 0; }
error = FT_New_Memory_Face(library,
data, /* first byte in memory */
size, /* size in bytes */
0, /* face_index */
&face );
if (error) { printf("Could not create a face"); return 0; }
// Sizing and Scaling testing
FT_Set_Char_Size(face, (FT_F26Dot6) 64, (FT_F26Dot6) 0, (FT_UInt) 0, (FT_UInt) 24);
FT_Set_Pixel_Sizes(face, (FT_UInt) 24, (FT_UInt) 12);
FT_Request_Size(face, &req);
FT_Select_Size(face, 0);
FT_Set_Transform(face, &matrix, &delta);
FT_Get_Transform(face, &matrix, &delta);
// Cleanup
FT_Done_Face(face);
FT_Done_FreeType(library);
return 0;
}
字形检索
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ft2build.h>
#include <math.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < 10) return 0; // Reject very small inputs
FT_Library library; // handle to library
FT_Face face; // handle to face object
FT_Error error; // hande to error
FT_UInt previous, glyph_index;
FT_Vector kerning;
FT_Fixed akerning;
// Init library
error = FT_Init_FreeType(&library);
if (error) { printf("Could not load the library"); return 0; }
error = FT_New_Memory_Face(library,
data, /* first byte in memory */
size, /* size in bytes */
0, /* face_index */
&face );
if (error) { printf("Could not create a face"); return 0; }
glyph_index = FT_Get_Char_Index(face, 0);
FT_Load_Glyph(face, 1, FT_LOAD_DEFAULT);
FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
FT_Get_Kerning(face, previous, glyph_index, FT_KERNING_DEFAULT, &kerning);
FT_Get_Track_Kerning(face, (FT_Fixed) 4, (FT_Int) 3, (FT_Fixed*) akerning);
// Cleanup
FT_Done_Face(face);
FT_Done_FreeType(library);
return 0;
}
API harness
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ft2build.h>
#include <math.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#define NUM_ENCODINGS (sizeof(supported_encodings) / sizeof(supported_encodings[0]))
#define MAX_GLYPHS 256
#define BUFFER_SIZE 1024
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < 10) return 0; // Reject very small inputs
FT_Library library;
FT_Face face;
FT_Error error;
FT_UInt glyph_index;
FT_Vector kerning, delta;
FT_Matrix matrix;
char buffer[BUFFER_SIZE];
// Supported encodings array
FT_Encoding supported_encodings[] = {
FT_ENCODING_NONE,
FT_ENCODING_MS_SYMBOL,
FT_ENCODING_UNICODE,
FT_ENCODING_SJIS,
FT_ENCODING_PRC,
FT_ENCODING_BIG5,
FT_ENCODING_WANSUNG,
FT_ENCODING_JOHAB,
FT_ENCODING_ADOBE_STANDARD,
FT_ENCODING_ADOBE_EXPERT,
FT_ENCODING_ADOBE_CUSTOM,
FT_ENCODING_ADOBE_LATIN_1,
FT_ENCODING_OLD_LATIN_2,
FT_ENCODING_APPLE_ROMAN
};
// Initialize FreeType library
error = FT_Init_FreeType(&library);
if (error) return 0;
// Create new face from memory
error = FT_New_Memory_Face(library, data, size, 0, &face);
if (error) {
FT_Done_FreeType(library);
return 0;
}
// Initialize transformation matrix
double angle = (data[0] % 360) * 3.14159 / 180.0; // Use first byte for rotation
matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L);
matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L);
matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L);
matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L);
// Initialize delta based on input data
delta.x = ((int16_t)(data[1] << 8 | data[2])) * 64;
delta.y = ((int16_t)(data[3] << 8 | data[4])) * 64;
// Test face properties
if (FT_HAS_HORIZONTAL(face)) {
// Get font metrics
FT_Size_RequestRec req;
req.type = FT_SIZE_REQUEST_TYPE_NOMINAL;
req.width = 0;
req.height = (data[5] % 32 + 8) * 64; // 8-40 pt size
req.horiResolution = 96;
req.vertResolution = 96;
FT_Request_Size(face, &req);
}
// Select and test different character maps
if (face->num_charmaps > 0) {
FT_Select_Charmap(face, supported_encodings[data[6] % NUM_ENCODINGS]);
// Get all available characters
FT_ULong charcode;
FT_UInt gindex;
charcode = FT_Get_First_Char(face, &gindex);
// Store up to MAX_GLYPHS characters for testing
FT_ULong charcodes[MAX_GLYPHS];
FT_UInt gindices[MAX_GLYPHS];
int num_chars = 0;
while (gindex != 0 && num_chars < MAX_GLYPHS) {
charcodes[num_chars] = charcode;
gindices[num_chars] = gindex;
num_chars++;
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
// Test kerning if available
if (FT_HAS_KERNING(face) && num_chars > 1) {
for (int i = 0; i < num_chars - 1 && i < 10; i++) { // Limit iterations
FT_Vector kern;
FT_Get_Kerning(face, gindices[i], gindices[i+1],
FT_KERNING_DEFAULT, &kern);
// Test track kerning
FT_Fixed track_kern;
FT_Get_Track_Kerning(face, face->size->metrics.x_ppem * 64,
-2, &track_kern);
}
}
// Test glyph loading and rendering
for (int i = 0; i < num_chars && i < 10; i++) { // Limit iterations
error = FT_Load_Char(face, charcodes[i], FT_LOAD_DEFAULT);
if (!error) {
FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
// If glyph names are available, test name functions
if (FT_HAS_GLYPH_NAMES(face)) {
FT_Get_Glyph_Name(face, gindices[i], buffer, BUFFER_SIZE);
FT_Get_Name_Index(face, (FT_String*)buffer);
}
// Test subglyph information if available
if (face->glyph->format == FT_GLYPH_FORMAT_COMPOSITE) {
FT_UInt index;
FT_Int p1, p2;
FT_UInt flags;
FT_Matrix submatrix;
for (int j = 0; j < face->glyph->num_subglyphs; j++) {
FT_Get_SubGlyph_Info(face->glyph, j, &index, &flags,
&p1, &p2, &submatrix);
}
}
}
}
}
// Test other face properties
if (FT_HAS_MULTIPLE_MASTERS(face)) {
// Could add Multiple Master specific tests here
}
if (FT_HAS_COLOR(face)) {
// Could add color-specific tests here
}
// Get PostScript name and FSType flags
FT_Get_Postscript_Name(face);
FT_Get_FSType_Flags(face);
// Cleanup
FT_Done_Face(face);
FT_Done_FreeType(library);
return 0;
}
技巧与建议
在开展 Fuzzing 项目时,可以考虑以下策略来优化你的方法:
利用已有成果
-
搜索现有 Harness:
在搜索引擎上查找前人研究者的工作,寻找可以借鉴或学习的 harness。
-
探索项目测试套件:
很多项目会实现自己的 Fuzzing 测试。查阅这些内容可以了解哪些区域已经被覆盖、哪些存在潜在空白,从而获得战略优势。
-
检查 OSS-Fuzz 覆盖范围:
查看该库或函数的哪些部分已被 OSS-Fuzz 覆盖。识别被忽略的区域可能带来有趣的发现。
通用技巧
-
构建健壮的语料库:
多样化且精心筛选的输入语料库是高效 Fuzzing 的关键。
-
优化编译:
-
将一小部分构建 (例如 1/15) 使用 AddressSanitizer (ASan) 编译,以获得更好的 bug 检测能力。
-
使用
-O3等优化标志来提高 Fuzzing 过程中的覆盖率。 -
使用高级工具:
考虑使用 Redqueen 进行输入与状态的对应分析,获取更深入的洞察。
-
优化系统:
遵循 AFL++ 性能优化建议,确保 Fuzzing 环境高效且性能良好。
-
寻找灵感:
从其他资源和文章中学习:
-
Fuzzing 技术与 Harness 编写
-
Awesome LibFuzzer Harness 合集
-
你可以在 这里 找到我为本文编写的更多 harness
进一步探索
很多研究者正在致力于解决 harness 创建的挑战,提出了各种自动化方法。你可以从以下论文中获得启发:
- 自动化 Fuzzing Harness 生成:面向库 API 和二进制协议解析器
- FuzzGen:自动化 Fuzzer 生成
- 自动化 Fuzzing Harness 生成:面向库 API 和二进制协议解析器
结论
编写 harness 不仅仅是运行工具——它关乎理解目标的细微之处、预判边界情况,并在实践中不断迭代学习。一个好的 Fuzzing harness 不会在第一次尝试时就完美,但精心构建的 harness 会逐步演变为发现 bug 和理解目标在压力下行为表现的不可或缺的工具。好的 harness 应当以最高的覆盖率和良好的执行速度为目标。它应该尽可能多地覆盖你想要测试的库代码,无论是通过多个小 harness 还是一个大型 harness。高效的 harness 还需要策略性——例如,你可以将计算密集型功能排除在通用 harness 之外,专注于常规函数以覆盖更多的边界情况,然后单独对重量级功能进行 Fuzzing。这些策略只能来自研究人员对其目标的深入经验。
遵循本文阐述的原则和策略,你不仅仅是在构建一个 harness——你正在为自己配备系统性地拆解假设、测试边界和发现他人可能遗漏的漏洞的能力。无论你的目标是知名库还是较为小众的项目,这种方法都为你提供了高效且有意义地进行 Fuzzing 的基础。编写 harness 并不光鲜——它是技术性的、迭代性的,偶尔还令人沮丧。但当崩溃报告开始源源不断地涌来时,你会知道一切努力都是值得的。
免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:securitainment Bushido Security Bushido Security《Fuzzing 的艺术:如何为库编写高效的 Harness》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论