LibAFL的安装及基本使用

news2025/1/16 3:46:23

本教程安装LibAFL使用的是Ubuntu 22.04 操作系统

1. 安装

1.1 Rust 安装

Rust的安装,参照Rust官网:https://www.rust-lang.org/tools/install

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

1.2 LLVM安装

直接apt安装,安装的应该是LLVM 14,LibAFL说是在LLVM 11-15之间就行。

sudo apt update
sudo apt-get install llvm clang

1.3 LibAFL安装

cargo install cargo-make
git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL
cargo build --release

自此安装完毕。

2. 基本使用

现在来使用这个LibAFL已经写好的模糊测试器(libfuzzer)来测试libpng。主要参考官方的这个教程:https://github.com/AFLplusplus/LibAFL/tree/main/fuzzers/libfuzzer_libpng

这个模糊测试器在LibAFL/fuzzers/libfuzzer_libpng目录下,先把他build起来。

cd fuzzers/libfuzzer_libpng
cargo build --release

这个操作会生成两个编译器(libafl_cc和libafl_cxx)的wrapper,需要使用他们来编译程序。他们会出现在target/release的文件夹下。

然后下载libpng,并解压

wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar -xvf libpng-1.6.37.tar.xz

然后使用libafl_cc编译器来编译libpng,

cd libpng-1.6.37
./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes
make CC="$(pwd)/../target/release/libafl_cc" CXX="$(pwd)/../target/release/libafl_cxx" -j `nproc`

然后可以发现编译后的静态库在这个目录下libpng-1.6.37/.libs/libpng16.a

因为我们测试的是libpng,它是一个库,所以还需要编译一个harness,来调用libpng的库。harness.cc位于fuzzers/libfuzzer_libpng下。

./target/release/libafl_cxx ./harness.cc libpng-1.6.37/.libs/libpng16.a -I libpng-1.6.37/ -o fuzzer_libpng -lz -lm

开始测试,先在一个终端运行下面的程序,它会开启一个tcp端口(1337),等待fuzzer 客户端连接,这个端口是本地的,目的只是用来初始化的握手。后续的通信是通过shared map。目前需要在libfuzzer_libpng的目录下运行,才可以访问到libpng的语料库。

./fuzzer_libpng

然后在另外开启另外一个终端,运行下面命令

./fuzzer_libpng

再切回到原来的终端,会发现开始跑模糊测试了。

这是第一个终端的界面,需要在另外一个终端执行./fuzzer_libpng才会出现下面的情况

在这里插入图片描述

另一个终端的界面

在这里插入图片描述

3. libfuzz_libpng是如何构造的

进入src目录下,会发现有lib.rs和bin目录,bin目录存放的是编译器wrapper的代码,也就是负责插桩的代码。lib.rs是构建fuzzer的代码。

3.1 libafl_cc.rs

简单来看就是个clang的包装器,加了个-fsanitize-coverage=trace-pc-guard选项。

下面是chatgpt对这部分代码的解释,感觉说的没啥问题。

这是一个Rust语言编写的程序,主要目的是作为一个编译器的包装器(wrapper)来调用Clang编译器,并在编译时链接静态库并进行覆盖率分析。
程序接受命令行参数作为输入,然后根据参数执行不同的操作。如果命令行参数的数量少于2,则程序会抛出一个panic异常。否则,程序会尝试解析命令行参数并使用Clang编译器进行编译。如果编译成功,则程序以编译器的返回代码(exit code)退出,否则程序也会抛出一个panic异常。
具体来说,程序首先使用Rust标准库中的env模块获取命令行参数,并检查是否至少传入了一个参数。接下来,程序通过调用ClangWrapper::new()方法创建一个ClangWrapper对象,然后根据包装器(wrapper)的名称来判断要使用C++编译器还是C编译器。如果包装器的名称以"cc"结尾,则使用C编译器,否则如果名称以"++"、"pp"或"xx"结尾,则使用C++编译器。如果无法确定应该使用哪种编译器,则程序会抛出一个panic异常。
然后,程序使用ClangWrapper对象的方法来添加链接静态库、设置覆盖率分析等编译选项,并运行编译器进行编译。如果编译器成功完成编译,则程序使用编译器的返回代码(exit code)退出。
总之,这个程序主要是作为一个包装器(wrapper)来调用Clang编译器,以便在编译时添加一些额外的选项。在这种情况下,它被用于编译fuzz测试。

use std::env;

use libafl_cc::{ClangWrapper, CompilerWrapper};

pub fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() > 1 {
        let mut dir = env::current_exe().unwrap();
        let wrapper_name = dir.file_name().unwrap().to_str().unwrap();

        let is_cpp = match wrapper_name[wrapper_name.len()-2..].to_lowercase().as_str() {
            "cc" => false,
            "++" | "pp" | "xx" => true,
            _ => panic!("Could not figure out if c or c++ wrapper was called. Expected {dir:?} to end with c or cxx"),
        };

        dir.pop();

        let mut cc = ClangWrapper::new();
        if let Some(code) = cc
            .cpp(is_cpp)
            // silence the compiler wrapper output, needed for some configure scripts.
            .silence(true)
            .parse_args(&args)
            .expect("Failed to parse the command line")
            .link_staticlib(&dir, "libfuzzer_libpng")
            .add_arg("-fsanitize-coverage=trace-pc-guard")
            .run()
            .expect("Failed to run the wrapped compiler")
        {
            std::process::exit(code);
        }
    } else {
        panic!("LibAFL CC: No Arguments given");
    }
}

3.2 lib.rs

lib.rs代码有点多,分段来看,首先导入库的部分,继承了LibAFL里很多有用的组件。

//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for libpng.
use mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;

use core::time::Duration;
#[cfg(feature = "crash")]
use std::ptr;
use std::{env, path::PathBuf};

use libafl::{
    bolts::{
        current_nanos,
        rands::StdRand,
        tuples::{tuple_list, Merge},
        AsSlice,
    },
    corpus::{Corpus, InMemoryCorpus, OnDiskCorpus},
    events::{setup_restarting_mgr_std, EventConfig, EventRestarter},
    executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
    feedback_or, feedback_or_fast,
    feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
    fuzzer::{Fuzzer, StdFuzzer},
    inputs::{BytesInput, HasTargetBytes},
    monitors::MultiMonitor,
    mutators::{
        scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
        token_mutations::Tokens,
    },
    observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
    schedulers::{
        powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler,
    },
    stages::{calibrate::CalibrationStage, power::StdPowerMutationalStage},
    state::{HasCorpus, HasMetadata, StdState},
    Error,
};
use libafl_targets::{libfuzzer_initialize, libfuzzer_test_one_input, EDGES_MAP, MAX_EDGES_NUM};

再看下主函数,主要是调用fuzz函数,传入了三个参数,分别是语料库的路径、崩溃文件目录以及随机种子。

pub fn libafl_main() {
    // Registry the metadata types used in this fuzzer
    // Needed only on no_std
    //RegistryBuilder::register::<Tokens>();

    println!(
        "Workdir: {:?}",
        env::current_dir().unwrap().to_string_lossy().to_string()
    );
    fuzz(
        &[PathBuf::from("./corpus")],
        PathBuf::from("./crashes"),
        1337,
    )
    .expect("An error occurred while fuzzing");
}

fuzz函数中,首先创建了MultiMonitor来打印调试信息。

let monitor = MultiMonitor::new(|s| println!("{s}"));

RestartingManager让目标程序在模糊测试过程中崩溃时重新启动程序。

 let (state, mut restarting_mgr) =
        match setup_restarting_mgr_std(monitor, broker_port, EventConfig::AlwaysUnique) {
            Ok(res) => res,
            Err(err) => match err {
                Error::ShuttingDown => {
                    return Ok(());
                }
                _ => {
                    panic!("Failed to setup the restarter: {err}");
                }
            },
        };

edges_observer使用覆盖率映射表来观察程序的执行情况

  let edges_observer = unsafe {
        HitcountsMapObserver::new(StdMapObserver::from_mut_ptr(
            "edges",
            EDGES_MAP.as_mut_ptr(),
            MAX_EDGES_NUM,
        ))
    };

判断输入是否有趣。主要是基于覆盖率和执行的时间来进行判断。

    let time_observer = TimeObserver::new("time");
    let map_feedback = MaxMapFeedback::new_tracking(&edges_observer, true, false);

    let calibration = CalibrationStage::new(&map_feedback);
    let mut feedback = feedback_or!(
        // New maximization map feedback linked to the edges observer and the feedback state
        map_feedback,
        // Time feedback, this one does not need a feedback state
        TimeFeedback::with_observer(&time_observer)
    );

判断输入是否是最终想要的,即是不是PoC。

let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new());

如果重启失败了,需要重新创建状态。

  let mut state = state.unwrap_or_else(|| {
        StdState::new(
            // RNG
            StdRand::with_seed(current_nanos()),
            // Corpus that will be evolved, we keep it in memory for performance
            InMemoryCorpus::new(),
            // Corpus in which we store solutions (crashes in this example),
            // on disk so the user can get them after stopping the fuzzer
            OnDiskCorpus::new(objective_dir).unwrap(),
            // States of the feedbacks.
            // The feedbacks can report the data that should persist in the State.
            &mut feedback,
            // Same for objective feedbacks
            &mut objective,
        )
        .unwrap()
    });

创建png字典。主要是libpng需要合法的png图片来作为输入。

if state.metadata().get::<Tokens>().is_none() {
        state.add_metadata(Tokens::from([
            vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
            "IHDR".as_bytes().to_vec(),
            "IDAT".as_bytes().to_vec(),
            "PLTE".as_bytes().to_vec(),
            "IEND".as_bytes().to_vec(),
        ]));
    }

构造一个具有多阶段的变异器。


  let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
  let power = StdPowerMutationalStage::new(mutator);
  let mut stages = tuple_list!(calibration, power);

从语料库获取种子的调度器(种子调度)

let scheduler = IndexesLenTimeMinimizerScheduler::new(StdWeightedScheduler::with_schedule(
        &mut state,
        &edges_observer,
        Some(PowerSchedule::FAST),
    ));

把前面的组件组装为1个fuzzer

let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

构造harness的wrapper,不太理解这段代码,推测是libfuzzer的特性所以需要这么一段。

  let mut harness = |input: &BytesInput| {
      let target = input.target_bytes();
      let buf = target.as_slice();
      #[cfg(feature = "crash")]
      if buf.len() > 4 && buf[4] == 0 {
          unsafe {
              eprintln!("Crashing (for testing purposes)");
              let addr = ptr::null_mut();
              *addr = 1;
          }
      }
      libfuzzer_test_one_input(buf);
      ExitKind::Ok
  };

构建一个超时的执行器,也就是在给定的时间内执行程序。

 let mut executor = TimeoutExecutor::new(
        InProcessExecutor::new(
            &mut harness,
            tuple_list!(edges_observer, time_observer),
            &mut fuzzer,
            &mut state,
            &mut restarting_mgr,
        )?,
        // 10 seconds timeout
        Duration::new(10, 0),
    );
 // The actual target run starts here.
 // Call LLVMFUzzerInitialize() if present.
  let args: Vec<String> = env::args().collect();
  if libfuzzer_initialize(&args) == -1 {
      println!("Warning: LLVMFuzzerInitialize failed with -1");
  }

处理下初始语料库为空的情况

   // In case the corpus is empty (on first run), reset
    if state.must_load_initial_inputs() {
        state
            .load_initial_inputs(&mut fuzzer, &mut executor, &mut restarting_mgr, corpus_dirs)
            .unwrap_or_else(|_| panic!("Failed to load initial corpus at {:?}", &corpus_dirs));
        println!("We imported {} inputs from disk.", state.corpus().count());
    }

迭代执行

 let iters = 1_000_000;
    fuzzer.fuzz_loop_for(
        &mut stages,
        &mut executor,
        &mut state,
        &mut restarting_mgr,
        iters,
    )?;

总的来说有点像搭积木的感觉,但是目前对每个积木怎么搭的还不是很了解,后续再看看别的fuzzer是怎么实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/379105.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day903.自增主键不能保证连续递增 -MySQL实战

自增主键不能保证连续递增 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于自增主键不能保证连续递增的内容。 MySql保证了主键是自增&#xff0c;但不相对连续&#xff1b;帮助开发人员快速识别每个行的唯一性&#xff0c;并提高查询效率。 自增主键可以让主键索引…

2023上半年北京/上海/广州/深圳NPDP产品经理认证报名

产品经理国际资格认证NPDP是国际公认的唯一的新产品开发专业认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年…

【GUI】Robo 3T(Studio 3T Free) for Mongodb安装与使用教程

下载 robo 3T现已更名为studio 3T free&#xff0c;官网即可下载 studio 3T free下载地址 安装 mac电脑下载的是dmg安装包&#xff0c;直接正常安装即可&#xff0c;windows电脑也是一样的&#xff0c;不需要配置环境&#xff0c;安装即可使用。&#xff08;前提是你已经安装…

【C++】二叉搜索树的模拟实现

一、概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值它的左右子树也分别…

Python---time模块

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;Python在学&#xff0c;希望能够得到各位的支持&#xff01;&#xff01;&#xff01; time模块前言时间戳time.time()将时间戳转换成字符串time.ctime()将时间戳转换为元组time.localtime(时间戳)将元…

大四、非计算机专业,Python该怎么学?

我是非计算机专业&#xff0c;大四时开始学Python&#xff0c;用了大概一个半月时间&#xff0c;现在从事数据挖掘工作&#xff0c;算是有点发言权。虽然之前学了点C&#xff0c;但仅仅是皮毛&#xff0c;为了应付考试&#xff0c;所以我基本是零基础开始学Python的。 总结学习…

Stress压力工具的部署及使用

Stress压力工具的部署及使用 下载地址&#xff1a;wget https://fossies.org/linux/privat/old/stress-1.0.5.tar.gz 1.部署 进入目录执行./autogen.sh [rootiZ2ze1pj93eyq389c2ppi5Z stress-1.0.5]# ./autogen.sh ps&#xff1a;如果执行过程中缺包&#xff0c;安装对应的…

运维自动化——Ansible

一&#xff1a;ansible命令执行过程 1. 加载自己的配置文件 默认/etc/ansible/ansible.cfg 2. 加载自己对应的模块文件&#xff0c;如command 3. 通过ansible将模块或命令生成对应的临时py文件&#xff0c; 并将该文件传输至远程服务器的对应执行用户 $HOME…

leetcode 51~60 学习经历

leetcode 51~60 学习经历51. N 皇后52. N 皇后 II53. 最大子数组和54. 螺旋矩阵55. 跳跃游戏56. 合并区间57. 插入区间58. 最后一个单词的长度59. 螺旋矩阵 II60. 排列序列小结51. N 皇后 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子…

【MobileNet】MobileNet V1

MobileNet V11、简介2、Depthwise Separable Convolution1&#xff09;Depthwise Separable Convolution 的优点2&#xff09;Depthwise Separable Convolution 网络结构3&#xff09;pytorch 函数 实现 depth-wise convolution2、Mobile 网络结构pytorch实现 Mobile 网络结构&…

hiveSQL开窗函数详解

hive开窗函数 文章目录hive开窗函数1. 开窗函数概述1.1 窗口函数分类1.2 窗口函数和普通聚合函数的区别2. 窗口函数的基本用法2.1 基本用法2.2 设置窗口的方法2.2.1 window_name2.2.2 partition by2.2.3 order by 子句2.2.4 rows指定窗口大小窗口框架2.3 开窗函数中加 order by…

Linux下使用Makefile实现条件编译

在Linux系统下Makefile和C/C语言都有提供条件选择编译的语法&#xff0c;就是在编译源码的时候&#xff0c;可以选择性地编译指定的代码。这种条件选择编译的使用场合有好多&#xff0c;例如我们开发一个兼容标准版本与定制版本兼容的项目&#xff0c;那么&#xff0c;一些与需…

[数据结构]:07-二叉树(无头结点)(C语言实现)

目录 前言 已完成内容 二叉树实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-QueueFunction.cpp 04-TreeFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代码。使用C引用主要是为了…

金三银四,助力你的大厂梦,2023年软件测试经典面试真题(3)(共3篇)

前言 金三银四即将到来&#xff0c;相信很多小伙伴要面临面试&#xff0c;一直想着说分享一些软件测试的面试题&#xff0c;这段时间做了一些收集和整理&#xff0c;下面共有三篇经典面试题&#xff0c;大家可以试着做一下&#xff0c;答案附在后面&#xff0c;希望能帮助到大…

【软件测试】从0到1的突破,appium自动化测试你真的会吗?自动化测试思路总结......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 总结一下APP UI自动…

超店有数分享:tiktok数据分析工具推荐,助你成功出海!

现阶段的跨境电商人都纷纷入局tiktok&#xff0c;这是风口也是发展趋势。Tiktok的下载量已经超过了35亿&#xff0c;每月都有10亿用户活跃&#xff0c;在154国家/地区使用。Tiktok用户每天在平均花1小时左右进行浏览&#xff0c;打开率也很高。如今&#xff0c;tiktok也越来越成…

酷雷曼VR丨十大“高含金量”荣誉,一起见证!

VR全景领域 十大“高含金量”荣誉 高光时刻 一同见证 01、双高新技术企业 同时获得国家高新技术企业、中关村高新技术企业双认证&#xff0c;是对酷雷曼企业研究开发组织管理水平、科技成果转化能力、自主知识产权数量、销售与总资产成长性等多维度实力的综合体现。 双高…

【MinIO】文件断点续传和分块合并

【MinIO】文件断点续传和分块合并 文章目录【MinIO】文件断点续传和分块合并0. 准备工作1. 检查文件是否存在1.1 定义接口1.2 编写实现方法2. 检查分块文件是否存在2.1 定义接口2.2 编写实现方法3. 上传分块文件接口3.1 定义接口3.2 编写实现方法4. 合并分块文件接口4.1 定义接…

如何判断一个客户是大客户?

米茂搜对主要外贸销售客户的识别方法整理如下&#xff1a;1. 确定研究目标。通过对客户数据的收集和分析&#xff0c;找出大客户&#xff0c;对大客户实施个性化管理&#xff0c;并对其服务进行跟踪&#xff0c;以及。不时地改善服务&#xff0c;以保持他们的忠诚度。2. 扩大信…

ChatGPT 引爆全网热议,如果当它是“聊天机器人”,那你可就错了

近日来&#xff0c;智能聊天机器人ChatGPT的出现引发众多网友讨论&#xff0c;那它到底是什么呢&#xff1f; 2022年11月&#xff0c;人工智能公司OpenAI推出了一款聊天机器人&#xff1a;ChatGPT。它能够通过学习和理解人类语言来进行对话&#xff0c;还能与聊天对象进行有逻…