二进制漏洞挖掘(WinAFL Fuzzing)Windows篇

文章目录

开篇简述

昨天发布了二进制漏洞挖掘(AFL Fuzzing)使用篇阅读量今日竟然达到了400+阅读私信的朋友也有在问Windows下如何使用AFL,本篇文章就重点将Windows下的AFL,就是WinAFL的安装与使用。

WinAFL编译与安装

1.AFL与WinAFL的区别

之前发布的二进制漏洞挖掘(AFL Fuzzing)使用篇使用的就是AFL与AFL++它们是专门针对于Linux环境下的支持源码插桩QEMU动态插桩WinAFL只支持DynamoRIO动态插桩不过也有一些文章配置可以将WinAFL进行源码插桩不过本篇文章不打算深入其中,只介绍使用最常用的WinAFL中的DynamoRIO动态插桩技术。

AFL维护是Michał Zalewski(Google)→ 现已无人维护。

AFL++维护是Marc "van Hauser" Heuse 等社区开发者。

WinAFL维护是Google Project Zero(GPZ) 是 Google 旗下的安全研究团队。

2.WinAFL与DynamoRIO准备

WinAFL项目

https://github.com/googleprojectzero/winafl

DynamoRIO官网

https://dynamorio.org/

3.WinAFL编译

下载好上述的源码之后就是安装编译环境需要Visual Studio和CMake这里我已经安装好了然后就是编译命令。

cmake -S . -B build -G "Visual Studio 17 2022" -A x64

-DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build --config Release

bash 复制代码
#第一步:配置项目(生成 Visual Studio 工程)
#编译的时候需要指定DynamoRIO
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DDynamoRIO_DIR="C:\Users\admin\Documents\WinAFL\DynamoRIO-Windows-11.3.0\DynamoRIO-Windows-11.3.0-1\cmake"

#第二步:编译项目
cmake --build build --config Release

这里编译遇到一个坑点2026年2月5日还在那就是跟DynamoRIO不兼容修复可以参考
https://github.com/googleprojectzero/winafl/issues/479

编译成功后有如下文件:

4.DynamoRIO安装

https://dynamorio.org/page_releases.html

访问之后下载对应的releases

下载zip解压之后是下面的目录

复制代码
DynamoRIO-Windows-11.3.0-1/
├── ACKNOWLEDGEMENTS    # 致谢名单
├── LICENSE.txt         # 许可证
├── README              # 简要说明
├── bin32/              # 32位核心工具
├── bin64/              # 64位核心工具 ⭐WinAFL主要使用
├── cmake/              # CMake配置文件
├── docs/               # 官方文档
├── drmemory/           # Dr.Memory工具
├── dynamorio/          # 运行时核心文件
├── ext/                # 扩展库
├── include/            # 开发头文件
├── lib32/              # 32位静态库
├── lib64/              # 64位静态库
├── logs/               # 日志输出
├── samples/            # 示例代码
└── tools/              # 额外工具

WinAFL使用

WinAFL使用-D命令指定DynamoRIO的bin64目录即可,完整的WinAFL命令如下:

bash 复制代码
afl-fuzz.exe ^
  -i in ^
  -o out ^
  -D "C:\path\to\DynamoRIO\bin64" ^
  -t 2000+ ^
  --coverage_module target.exe ^
  --target_module target.exe ^
  --target_offset 0x1000 ^
  --fuzz_iterations 5000 ^
  -f 1024 ^
  -- target.exe @@
参数 示例值 说明
-i in 输入目录:存放种子文件的文件夹
-o out 输出目录:AFL输出结果(崩溃、超时、队列)
-D C:\DynamoRIO\bin64 DynamoRIO路径:插桩引擎所在目录
--coverage_module target.exe 覆盖率模块:记录代码覆盖率的模块名(可多个,逗号分隔)
--target_module target.exe 目标模块:包含目标函数的模块(用于计算偏移)
--target_offset 0x1000 函数偏移:目标函数在模块内的RVA地址(十六进制)
-- -- 分隔符:区分AFL参数和目标程序参数
target.exe target.exe 目标程序:被测试的可执行文件
@@ @@ 输入占位符:会被替换为变异后的文件路径

可选参数

参数 示例值 说明
-t 2000+ 超时时间 :毫秒,+表示自动调整(默认1000)
--fuzz_iterations 5000 迭代次数:每次持久模式循环执行次数
-f 1024 内存模式 :数据大小(字节),不用@@时用
-m none 内存限制 :目标程序内存限制(默认50MB,none不限)
--target_method fuzz 函数名:用导出函数名代替偏移(优先于offset)
--nargs 2 参数数量:目标函数的参数个数(默认2)
-Y 启用网络模式:测试网络程序
-w 100 网络超时:网络模式的超时时间
-p 1234 目标端口:网络模式的目标端口

1.WinAFL与Wrapper(包装函数)

通过上面的参数基本了解了WinAFL有哪些功能,WinAFL在Fuzzing的时候只会传入第一个参数进行模糊测试,比如一个函数第一个参数是文件路径第二个参数是大小第三个是类型,那么这个情况下可以直接使用WinAFL进行Fuzzing因为WinAFL默认情况下只会对第一个参数进行种子变异模糊测试,那么如果第一个参数是文件大小第二个参数是文件路径不就不可以了吗,这个时候就出现了Wrapper技术他在Fuzzing是非常常见的,本质上就是在实现一个程序这个程序接受一个参数由WinAFL提供然后自己程序在此调用需要模糊测试的比如某个DLL的导出函数顺序这里自己就可以随意控制了,这就是Wrapper技术。

2.使用Wrapper进行Fuzzing漏洞DLL

这里就要开始实战演示环节了需要用到2个程序分别是Wrapper包装函数用来与WinAFL做沟通,第2个就是要真正测试的程序这里我设计成了一个DLL然后有一个漏洞缺陷的导出函数来演示。
Wrapper程序源码

cpp 复制代码
// main.c: WinAFL harness wrapper for dlldemo
// 
// 演示目的:展示为什么需要 
// 
// 目标函数需要 3 个参数:
//   1. int size      - 数据大小
//   2. char* filepath - 文件路径
//   3. int type      - 操作类型
// 
// 但 WinAFL fuzzer 只能提供原始字节流,
// wrapper 的作用就是:把原始字节解析成目标函数需要的参数格式

#include "pch.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include "dlldemo.h"

#define MAX_INPUT_SIZE 4096
#define MAX_PATH_SIZE 256

// 函数指针类型
typedef void (*vulnerable_function_t)(int size, char* filepath, int type);

// 从原始字节流解析参数的简易协议:
// [0-3]字节: int size (小端序)
// [4]字节:   int type 
// [5+]字节:  filepath字符串 (以\0结尾)
int parse_input(unsigned char* data, size_t len, int* size, int* type, char** filepath)
{
    if (len < 6) {
        printf("输入数据太短\n");
        return -1;
    }
    
    // 解析前4字节为 size
    *size = *(int*)data;
    
    // 第5字节为 type
    *type = data[4];
    
    // 剩余部分为 filepath
    *filepath = (char*)(data + 5);
    
    // 确保 filepath 以 \0 结尾
    data[len - 1] = '\0';
    
    printf("[Wrapper解析] size=%d, type=%d, filepath=%s\n", *size, *type, *filepath);
    return 0;
}

int main(int argc, char** argv)
{
    HMODULE hDll;
    vulnerable_function_t vulnerableFunc;
    FILE* fp;
    unsigned char* buffer = NULL;
    size_t fileSize;
    int size, type;
    char* filepath = NULL;

    if (argc < 2) {
        printf("用法: %s <输入文件>\n", argv[0]);
        printf("WinAFL Wrapper 演示程序\n");
        printf("作用:将 fuzzer 生成的原始字节流解析为函数参数\n");
        return 1;
    }

    // 加载 DLL
    hDll = LoadLibraryA("dlldemo.dll");
    if (hDll == NULL) {
        printf("错误:无法加载 dlldemo.dll\n");
        return 1;
    }

    // 获取漏洞函数地址
    vulnerableFunc = (vulnerable_function_t)GetProcAddress(hDll, "vulnerable_function");
    if (vulnerableFunc == NULL) {
        printf("错误:无法获取 vulnerable_function 地址\n");
        FreeLibrary(hDll);
        return 1;
    }

    // 读取 fuzzer 生成的输入文件
    fopen_s(&fp, argv[1], "rb");
    if (fp == NULL) {
        printf("错误:无法打开输入文件: %s\n", argv[1]);
        FreeLibrary(hDll);
        return 1;
    }

    fseek(fp, 0, SEEK_END);
    fileSize = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    if (fileSize == 0 || fileSize > MAX_INPUT_SIZE) {
        printf("错误:输入文件大小无效\n");
        fclose(fp);
        FreeLibrary(hDll);
        return 1;
    }

    buffer = (unsigned char*)malloc(fileSize);
    if (buffer == NULL) {
        printf("错误:内存分配失败\n");
        fclose(fp);
        FreeLibrary(hDll);
        return 1;
    }

    fread(buffer, 1, fileSize, fp);
    fclose(fp);

    // ==== 关键:Wrapper 的核心作用 ====
    // 把 fuzzer 的原始字节流转换成目标函数需要的参数
    if (parse_input(buffer, fileSize, &size, &type, &filepath) != 0) {
        free(buffer);
        FreeLibrary(hDll);
        return 1;
    }

    // 调用 DLL 中的漏洞函数
    printf("[Wrapper调用] 正在调用 vulnerable_function(%d, \"%s\", %d)...\n", 
           size, filepath, type);
    
    vulnerableFunc(size, filepath, type);
    
    printf("[Wrapper] 函数调用完成\n");

    // 清理
    free(buffer);
    FreeLibrary(hDll);
    
    return 0;
}

漏洞演示DLL代码

cpp 复制代码
#ifndef DLLEMO_H
#define DLLEMO_H

#ifdef DLLEMO_EXPORTS
#define DLLEMO_API __declspec(dllexport)
#else
#define DLLEMO_API __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif

DLLEMO_API void vulnerable_function(int size, char* filepath, int type);

#ifdef __cplusplus
}
#endif

#endif // DLLEMO_H
cpp 复制代码
// dllmain.c: DLL application entry point

#include "pch.h"
#include <windows.h>

// 存在缓冲区溢出漏洞的函数
// 参数1: size - 声称的数据大小
// 参数2: filepath - 文件路径字符串
// 参数3: type - 操作类型
DLLEMO_API void vulnerable_function(int size, char* filepath, int type)
{
    char buffer[16];
    
    // 漏洞:盲目信任 size 参数,没有验证实际长度
    // 当 type == 1 时,使用 strcpy 复制 filepath
    // 如果 filepath 实际长度 > 16,就会发生缓冲区溢出
    
    if (type == 1) {
        // 危险操作:没有检查 filepath 长度
        printf("[类型1] 处理文件: %s, 声称大小: %d\n", filepath, size);
        strcpy(buffer, filepath);  // 漏洞点!filepath 太长会溢出
        printf("缓冲区内容: %s\n", buffer);
    }
    else if (type == 2) {
        // 另一种操作
        printf("[类型2] 仅检查文件路径长度\n");
        if (strlen(filepath) < 16) {
            strcpy(buffer, filepath);
            printf("安全复制: %s\n", buffer);
        } else {
            printf("路径太长,已拒绝\n");
        }
    }
    else {
        printf("[类型%d] 未知操作类型\n", type);
    }
}

// DLL entry point
BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

将上面程序编译好之后就可以使用WinAFL配合DynamoRIO对wrapper进行二进制插桩对DLL导出函数vulnerable_function进行模糊测试。

创建种子:A到seed.txt

cpp 复制代码
mkdir in out
echo AAAAAAAAAAAAAAAAAAAAAAA > in\seed.txt

我这里将exe的动态基址关闭了方便后面调试,我要进行Fuzzing的位置是wrapper的main位置所以需要知道具体偏移这里是0x1070,没有选择Fuzzing DLL是因为WinAFL只是对一个参数进行Fuzzing,通过上面的代码其实就可以看出了第一个参数是大小而我们是要对第二个参数文件路径来传入我们的畸形数据来测试的,需要注意的是WinAFL支持两种模式传入种子数据分别是文件模式内存模式,@@参数就是文件模式,内存模式即直接传入缓冲区比如接受参数是byte * buffer这种不需要@@数据直接写内存不需要IO。
文件模式命令:

cpp 复制代码
afl-fuzz.exe -i in -o out -D "C:\Users\admin\Documents\WinAFL\WinAFL\WinAFL\DynamoRIO-Windows-11.3.0\DynamoRIO-Windows-11.3.0-1\bin64" -t 20000 -- -coverage_module wrapperdemo.exe -target_module wrapperdemo.exe -target_offset 0x1070 -fuzz_iterations 20 -- wrapperdemo.exe @@

这里其实就跟我之前写的AFL一样了找到了一个crashes,后续的调试可以参考我的
二进制漏洞挖掘AFL其实套路是一样的后续只不过从GDB调试改成了XDBG调试来观察崩溃位置。

相关推荐
原来是你~呀~3 小时前
CAI:人机协作的模块化网络安全AI框架
网络安全·自动化渗透测试
模型时代3 小时前
Linux系统安全革命:Amutable公司推出全新验证完整性技术
linux·运维·系统安全
xixixi777773 小时前
今日 AI 、通信、安全前沿日报(2026 年 2 月 5 日,星期四)
人工智能·网络安全·ai·信息安全·大模型·通信·前沿
浩浩测试一下6 小时前
内网---> ForceChangePassword 权限滥用
java·服务器·网络·安全·web安全·网络安全·系统安全
NOVAnet20236 小时前
南凌科技「Bot防护」:让恶意爬虫、刷票薅羊毛等自动化攻击无处遁形
爬虫·科技·网络安全·自动化·南凌科技
Whoami!6 小时前
⓫⁄₁₂ ⟦ OSCP ⬖ 研记 ⟧ Windows权限提升 ➱ 未加引号服务路径漏洞利用(下)
网络安全·信息安全·windows服务·未加引号服务路径
ん贤6 小时前
双Token的致命漏洞,你的系统安全吗?
安全·系统安全
深盾科技6 小时前
Windows 11 24H2内核堆栈保护:系统安全新盾牌
安全·系统安全
ccino .6 小时前
【官方最新VMware workstation pro获取】
运维·网络安全·自动化