LibAFL的安装及基本使用

本教程安装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是怎么实现的。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破落之实

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值