标题: [AFL]函数 ck_strdup 的作用 分类: Fuzzing 创建: 2022-10-23 01:21 修改: 2022-10-25 22:42 链接: http://0x2531.tech/fuzzing/202210230121.txt -------------------------------------------------------------------------------- 在 afl-fuzz 中,第一次调用函数 ck_strdup 是在处理传入的 fuzzer id,而之前的输入目录和输出 目录却未调用函数处理。那这个 ck_strdup 函数到底做了哪些事情呢? 首先,ck_strdup 只是别名,通过宏定义指向真正的函数 DFL_ck_strdup。 #define ck_strdup DFL_ck_strdup 接下来,重点分析下函数 DFL_ck_strdup /* Create a buffer with a copy of a string. Returns NULL for NULL inputs. */ static inline u8* DFL_ck_strdup(u8* str) { void* ret; u32 size; if (!str) return NULL; size = strlen((char*)str) + 1; ALLOC_CHECK_SIZE(size); ret = malloc(size + ALLOC_OFF_TOTAL); ALLOC_CHECK_RESULT(ret, size); ret += ALLOC_OFF_HEAD; ALLOC_C1(ret) = ALLOC_MAGIC_C1; ALLOC_S(ret) = size; ALLOC_C2(ret) = ALLOC_MAGIC_C2; return memcpy(ret, str, size); } 注意该函数定义引入了 inline 修饰符,表示是内联函数。在 GCC / Clang 编译程序时,该函数代码会 被直接嵌入在调用上下文,这样就减少了函数调用开销。 首先是计算字符串长度,C 语言中字符串约定以空字符('\0')结束,但标准函数 strlen 不计算空字符, 因此需 +1。 size = strlen((char*)str) + 1; 然后就是真正的功能代码。先检查字符串长度,如果大于 1GB,则直接报错退出。 ALLOC_CHECK_SIZE(size); #define ALLOC_CHECK_SIZE(_s) do { \ if ((_s) > MAX_ALLOC) \ ABORT("Bad alloc request: %u bytes", (_s)); \ } while (0) #define MAX_ALLOC 0x40000000 <--- 1GB 接着调用函数 malloc 分配内存,并检查可能发生的 OOM (out of memory) 错误 ret = malloc(size + ALLOC_OFF_TOTAL); ALLOC_CHECK_RESULT(ret, size); #define ALLOC_CHECK_RESULT(_r, _s) do { \ if (!(_r)) \ ABORT("Out of memory: can't allocate %u bytes", (_s)); \ } while (0) 然后是在特定位置设置一些 Magic tokens,用来保护内存。类似于 GCC 为实现栈保护(stack protector)引入的金丝雀(canary)值。 ret += ALLOC_OFF_HEAD; ALLOC_C1(ret) = ALLOC_MAGIC_C1; <--- (((u32*)(ret))[-2]) = 0xFF00FF00 ALLOC_S(ret) = size; <--- (((u32*)(ret))[-1]) = size ALLOC_C2(ret) = ALLOC_MAGIC_C2; <--- (((u8*)(ret))[size) = 0xF0 为了具体说明内存空间的布局,我们举个例子 现有字符串 "hi",则 size = 3,malloc 会分配 12 字节的内存空间 ((u32*)ret)[-2] ((u8*)ret)[3] | | ------------------------------------- |00|FF|00|FF|03|00|00|00|xx|xx|xx|FO| ------------------------------------- | | | ret ((u32*)ret)[-1] ret=ret+8 通过上图能够清晰的看到内存空间的布局(小端法) - 前4个字节(双字)存储特殊值 0xFF00FF00,表示已使用头部 - 紧接着4个字节(双字)存储字符串长度 - 紧接着的内存空间(xx)存储字符串值 - 末尾1个字节存储特殊值 0xF0,表示已使用尾部 最后,afl-fuzz 提供了和 ck_strdup 配合使用的 ck_free 函数,用于安全的释放内存。 #define ck_free DFL_ck_free /* Free memory, checking for double free and corrupted heap. When DEBUG_BUILD is set, the old memory will be also clobbered with 0xFF. */ static inline void DFL_ck_free(void* mem) { if (!mem) return; CHECK_PTR(mem); #ifdef DEBUG_BUILD /* Catch pointer issues sooner. */ memset(mem, 0xFF, ALLOC_S(mem)); #endif /* DEBUG_BUILD */ ALLOC_C1(mem) = ALLOC_MAGIC_F; free(mem - ALLOC_OFF_HEAD); } 一开始是使用宏 CHECK_PTR 执行内存检查 #define CHECK_PTR(_p) do { \ if (_p) { \ if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) {\ if (ALLOC_C1(_p) == ALLOC_MAGIC_F) \ ABORT("Use after free."); \ else ABORT("Corrupted head alloc canary."); \ } \ if (ALLOC_C2(_p) ^ ALLOC_MAGIC_C2) \ ABORT("Corrupted tail alloc canary."); \ } \ } while (0) 先检查前4个字节是否为 0xFF00FF00,如果不是,再检查是否为 0xFE00FE00,如果是则表示内存块已 被 free,否则表示头部金丝雀值已被改变,内存被越界写。然后检查最后一个字节是否为 0xF0,如果不是 则表示尾部金丝雀值已被改变,内存被越界写。 检查通过后,将前4个字节改写为 0xFE00FE00,标识该内存块已被 free,不可再次被 free(double free)。最后,free 该内存块。