本文是对Winafl源码的分析
image-20200426121601823

1.前言

winafl 是 afl 在 windows 的移植版, winafl 主要使用dynamorio(也可以使用IntelPT,和静态插桩)来统计代码覆盖率,并且使用共享内存的方式让fuzzer知道每个测试样本的覆盖率信息。这里主要分析winafl 不同于 afl 的部分,对于 afl 的变异策略等部分没有介绍,对于 afl 的分析可以看1.

2.程序框架

winafl 主要分为两个部分 afl-fuzz.c 和 winafl.c,前者是 fuzzer的主程序(负责调度和变异策略的具体实现),后者是收集程序运行时信息的dynamorio插件的源码(作为DR的插件运行).
实际运行时,进程之间的父子关系如下。

afl-fuzz 
   |-------winafl(drun -c winafl.dll )
                |-------  fuzz_target 

同时AFL-fuzzwinafl之间通过命名管道通信。

  1. afl-fuzz.exe通过创建命名管道与内存映射来实现与目标进程(DR)交互,其中管道用来发送和接收命令相互操作对方进程,内存映射主要用来记录覆盖率信息;
  2. 覆盖率记录主要通过drmgr_register_bb_instrumentation_event去设置BB执行的回调函数,通过instrument_bb_coverage或者instrument_edge_coverage来记录覆盖率情况,如果发现新的执行路径,就将样本放入队列目录中,用于后续文件变异,以提高代码覆盖率;
  3. 目标进程执行到目标函数后,会调用pre_fuzz_handler来存储上下文信息,包括寄存器和运行参数;
    目标函数退出后,会调用post_fuzz_handler函数,记录恢复上下文信息,以执行回原目标函数,又回到第2步;4. 目录函数运行次数达到指定循环调用次数时,会中断进程退出再启动一个新的进程。

3. AFL-Fuzz 主程序逻辑

3.1 main

int main(int argc, char** argv) {

  // 加载变异数据修正模块,这里通过引入`POST_LIBRARY`的`afl_postprocess`的函数指针,在`common_fuzz_stuff`函数中如果有,就会在`write_totestcase`前置,可以用来对样本进行预处理。
  setup_post();
  if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE); // MAP_SIZE --> 0x00010000
  setup_shm();  // 设置共享内存,用IntelPT的话情况不太一样
  init_count_class16();

  setup_dirs_fds(); // 设置模糊测试过程中的文件存放位置
  read_testcases();  // 读取测试用例到队列

  // 首先跑一遍所有的测试用例, 记录信息到样本队列
  perform_dry_run(use_argv);

  // 模糊测试主循环
  while (1) {
    u8 skipped_fuzz;
    // 每次循环从样本队列里面取测试用例
    cull_queue();

    // 对测试用例进行测试
    skipped_fuzz = fuzz_one(use_argv);
    if (!stop_soon && sync_id && !skipped_fuzz) {

      if (!(sync_interval_cnt++ % SYNC_INTERVAL))
        sync_fuzzers(use_argv);//如果存在并行的Fuzzers任务,同步一些样本

    }
    queue_cur = queue_cur->next;
    current_entry++;
  }
}

主要逻辑如下:

  • 首先设置一些 fuzz 过程中需要的状态值,比如共享内存、输入输出位置。
  • 然后通过 perform_dry_run 把提供的所有测试用例让目标程序跑一遍,同时统计执行过程中的覆盖率信息。
  • 之后就开始进行模糊测试的循环,每次取样本出来,然后交给fuzz_one对该样本进行 fuzz .
  • Fuzz_one是对一个队列进行一轮的Fuzz,这一轮里面包括calibrate,Trim,Bitflip
  • fuzz_one里面每个具体执行,都是通过common_fuzz_staff来执行的

3.2 run_target

前面提到了common_fuzz_staff调用 write_to_testcase 先把测试用例写入文件,默认情况下测试用例会写入 .cur_input (用户可以使用 -f 指定)
然后调用 run_target 让目标程序处理测试用例,其主要代码如下

static u8 run_target(char** argv, u32 timeout) {

  // 如果DR子进程还存活就不去创建新的进程
  if(!is_child_running()) {
    destroy_target_process(0);
    create_target_process(argv);  // 创建进程并且使用 dynamorio 监控
    fuzz_iterations_current = 0;
  }

  if (custom_dll_defined)
      process_test_case_into_dll(fuzz_iterations_current);

  child_timed_out = 0;
  memset(trace_bits, 0, MAP_SIZE);

  result = ReadCommandFromPipe(timeout);
  if (result == 'K')
  {
      //a workaround for first cycle in app persistent mode
      result = ReadCommandFromPipe(timeout);
  }

  // 当 winafl.dll 插桩准备好以后, 会通过命名管道发送 P 
  if (result != 'P')
  {
      FATAL("Unexpected result from pipe! expected 'P', instead received '%c'\n", result);
  }

  // 让 winafl.dll 那端开始继续执行
  WriteCommandToPipe('F');
 //在winafl(DR)的Pre_fuzz_handler,准备好了之后就会发送P命令,然后aflfuzz给DR发F
  result = ReadCommandFromPipe(timeout); 
  // 经由命名管道从DR子进程接收到 K 就表示该用例运行正常
  if (result == 'K') return FAULT_NONE;

  if (result == 'C') {
      destroy_target_process(2000);
      return FAULT_CRASH;
  }

  destroy_target_process(0);
  return FAULT_TMOUT;
}

首先会去判断目标进程是否还处于运行状态,如果不处于运行状态就新建目标进程,因为在 fuzz 过程中为了提升效率 ,会使用 dynamorio 来让目标程序不断的运行指定的函数,所以不需要每次 fuzz 都起一个新的进程。
然后如果需要使用用户自定义的方式发送数据。 就会使用 process_test_case_into_dll 发送测试用例,比如 fuzz 的目标是网络应用程序。

static int process_test_case_into_dll(int fuzz_iterations)
{

  char *buf = get_test_case(&fsize);

  result = dll_run_ptr(buf, fsize, fuzz_iterations); /* caller should copy the buffer */

  free(buf);

  return 1;
}

这个 dll_run_ptr 在用户通过 -l 提供了dll 的路径后,winafl 会通过 load_custom_library 设置相关的函数指针

void load_custom_library(const char *libname)
{
  int result = 0;
  HMODULE hLib = LoadLibraryA(libname);
  dll_init_ptr = (dll_init)GetProcAddress(hLib, "_dll_init@0");

  dll_run_ptr = (dll_run)GetProcAddress(hLib, "_dll_run@12");
}

winafl 自身也提供了两个示例(在custom_net_fuzzercustom_winafl_server中)分别是 tcp 服务和 tcp 客户端。在 dll_run_ptr 中也可以实现一些协议的加解密算法,这样就可以 fuzz 数据加密的协议了。

在一切准备好以后 winafl 往命名管道里面写入 F ,通知 winafl.dll (winafl 中实现代码覆盖率获取的dynamorio 插件)运行测试用例并记录覆盖率信息。 winafl.dll 执行完目标函数后会通过命名管道返回一些信息, 如果返回 K 表示用例没有触发异常,如果返回 C 表明用例触发了异常。

在 run_target 函数执行完毕之后, winafl 会对用例的覆盖率信息进行评估,然后更新样本队列。

3.3 calibrate_case

这个函数的功能是校准一个测试用例,判定一个测试用例是否存在行为不一致的问题。
行为不一致指的是calibrate_case函数会将同一个sample输入执行8次,观察同一样例下,会不会产生不同的覆盖率情况。(AFL假定,针对同一个样例在target_method过程中,在coverage_module的覆盖情况是一致),如果不一致的话,就被认定为variable behavior,这对指导变异是有害,因为无法判断新路径是真的还是假的,所以这个variable behavior越多stability越低。写wrapper时就要考虑同一个样本下,选取target_method下的行为要一致。
该函数被执行的条件:

  • 该函数在处理输入目录时会被调用,用于提前预告测试是否存在一些问题.
  • 在AFL发现新路径时会检测是否存在行为变化等。
static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem,
                         u32 handicap, u8 from_queue) {
  if (q->exec_cksum) memcpy(first_trace, trace_bits, MAP_SIZE);
  start_us = get_cur_time_us();
  for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
    write_to_testcase(use_mem, q->len);//写入testcase吗?
    fault = run_target(argv, use_tmout);//执行target
    cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);//计算当前的hash值
    if (q->exec_cksum != cksum) {
      u8 hnb = has_new_bits(virgin_bits);
      if (q->exec_cksum) {
          //不是第一次,即确认两次执行同一样本出现了不同覆盖率
        for (i = 0; i < MAP_SIZE; i++)
          if (!var_bytes[i] && first_trace[i] != trace_bits[i]) {
            var_bytes[i] = 1;
            stage_max    = CAL_CYCLES_LONG; //将执行次数提高到40次
          }
        var_detected = 1;
      } else {
          //第一次计算时候,还没有cksum
          //保留第一次的trace_bits
        q->exec_cksum = cksum;
        memcpy(first_trace, trace_bits, MAP_SIZE);
      }
    }
  }//计算平均用时
  update_bitmap_score(q);
abort_calibration:
  if (new_bits == 2 && !q->has_new_cov) {
    q->has_new_cov = 1;
    queued_with_cov++;
  }
  if (var_detected) {
    var_byte_count = count_bytes(var_bytes);
    if (!q->var_behavior) {
      mark_as_variable(q);//标记为variable_behavior
    }
  }
}

3.4 stability

stability 是一个和很重要的概念,稳定性,这个问题在linux上好像影响小,但是windows很多,它是指,对同一个输入样例进行多次测试,观察覆盖率是否有变化。从理论上,不应该出现不一致的情况,但是如果在windows上,存在独立于文件输入内容外的变量影响的话,就有可能是导致一个输入样本在运行时代码覆盖不一致,也就是执行路径不一致。毕竟在windows没有fork机制,为了在内存中快速执行目标函数DR是强制恢复程序状态来实现Fuzz的。

t_bytes = count_non_255_bytes(virgin_bits);
  t_byte_ratio = ((double)t_bytes * 100) / MAP_SIZE;

  if (t_bytes)
    stab_ratio = 100 - ((double)var_byte_count) * 100 / t_bytes;//计算stability
  else
    stab_ratio = 100;

virgin_bits指的是没有被Fuzz覆盖到的路径,每一个样本执行完之后,都需要计算当前的bitmap是否有产生新路径,负责计算是否有新路径的函数是has_new_bits这个函数,这个函数将位运算运用得非常好。下面举32位进行分析

static inline u8 has_new_bits(u8* virgin_map) {
u32* current = (u32*)trace_bits;
u32* virgin  = (u32*)virgin_map;
u32  i = (MAP_SIZE >> 2);
u8   ret = 0;
  while (i--) {
       if (*current && (*current & *virgin)) {
            if (ret < 2) {
                u8* cur = (u8*)current;
                u8* vir = (u8*)virgin;
                if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) ||
                (cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff)) ret = 2;
                else ret = 1;
            }
            *virgin &= ~*current;//取反然后再与的操作十分到位
       }
    current++;
    virgin++;
  }
  if (ret && virgin_map == virgin_bits) bitmap_changed = 1;
  return ret;
}

一开始virgin_map全部字节赋值为0xff,bit_map赋值为0,下面以bit_map中4个字节代表的基本块覆盖为例。

  1. 初始化时,virgin->|0xFF|0xFF|0xFF|0xFF|, bit_map->|0x00|0x00|0x00|0x00|
  2. 假设初次命中两个字节代表的基本块,则bit_map->|0x01|0x00|0x00|0x01|
  3. has_new_bits函数中,以4字节为单位让virgin_map[i] & bit_map[i],然后再4个字节内判定,如果vir[index]==0xFF,就可以认定为是第一次触发(ret=2)
  4. 然后位运算上场,*virgin &= ~*current,virgin->|0xFE|0xFF|0xFF|0xFE|, bit_map->|0x01|0x00|0x00|0x01|,这样的话如果下次依然是第0和第3字节发生变化,变成2了,可以识别出来,变回去0了,也不会判定为新路径。

这样我们再来看stability的计算,先用count_non_255_bytes计算出已经覆盖了的基本块位图数,然后再用var_byte_count做分子进行百分比计算.

4. winafl.c DR插桩程序分析

这个文件里面包含了 winafl 实现的 dynamorio 插件,里面实现覆盖率搜集以及一些模糊测试的效率提升机制。这是DR的插件,很多函数都是用到了DRAPI,DR整体还是比较复杂的,找时间再另外总结分析。

4.1 dr_client_main

入口函数是 dr_client_main

DR_EXPORT void
dr_client_main(client_id_t id, int argc, const char *argv[])
{
    drmgr_init();
    drx_init();
    drreg_init(&ops);
    drwrap_init();
    options_init(id, argc, argv);
    dr_register_exit_event(event_exit);
    drmgr_register_exception_event(onexception);
    if(options.coverage_kind == COVERAGE_BB) {
        drmgr_register_bb_instrumentation_event(NULL, instrument_bb_coverage, NULL);
    } else if(options.coverage_kind == COVERAGE_EDGE) {
        drmgr_register_bb_instrumentation_event(NULL, instrument_edge_coverage, NULL);
    }
    drmgr_register_module_load_event(event_module_load);
    drmgr_register_module_unload_event(event_module_unload);
    dr_register_nudge_event(event_nudge, id);
    client_id = id;
    if (options.nudge_kills)
        drx_register_soft_kills(event_soft_kill);
    if(options.thread_coverage) {
        winafl_data.fake_afl_area = (unsigned char *)dr_global_alloc(MAP_SIZE);
    }
    if(!options.debug_mode) {
        setup_pipe();
        setup_shmem();
    } else {
        winafl_data.afl_area = (unsigned char *)dr_global_alloc(MAP_SIZE);
    }
    if(options.coverage_kind == COVERAGE_EDGE || options.thread_coverage || options.dr_persist_cache) {
        winafl_tls_field = drmgr_register_tls_field();
        if(winafl_tls_field == -1) {
            DR_ASSERT_MSG(false, "error reserving TLS field");
        }
        drmgr_register_thread_init_event(event_thread_init);
        drmgr_register_thread_exit_event(event_thread_exit);
    }
    event_init();
}

主要逻辑如下:

  1. 首先会初始化一些 dynamorio 的信息, 然后根据用户的参数来选择是使用基本块覆盖率(instrument_bb_coverage)还是使用边覆盖率(instrument_edge_coverage)。
  2. 然后再注册一些事件的回调,例如CreateFile这类函数的回调分析。
  3. 之后就是设置命名管道和共享内存以便和 afl-fuzz 进行通信。

4.2 覆盖率记录

通过drmgr_register_bb_instrumentation_event我们就可以在每个基本块执行之前调用我们设置回调函数。这时我们就可以统计覆盖率信息了。具体的统计方式如下:
instrument_bb_coverage的方式

// 计算基本块的偏移并且取  MAP_SIZE 为数, 以便放入覆盖率表
offset = (uint)(start_pc - mod_entry->data->start);
offset &= MAP_SIZE - 1; // 把地址映射到 map中
afl_map[offset]++

这里的offset和MAP_SIZE(64KB)与一下,所以插桩的dll如果空间很大,这样bitmap冲突的概率就会增高。
instrument_edge_coverage的方式

offset = (uint)(start_pc - mod_entry->data->start);
offset &= MAP_SIZE - 1; // 把地址映射到 map中
afl_map[pre_offset ^ offset]++
pre_offset = offset >> 1

4.3 非presistence模式

在非持续模式下,在目标函数 to_wrap 执行前调用 pre_fuzz_handler 函数, 在目标函数执行后调用 post_fuzz_handler 函数。

static void
pre_fuzz_handler(void *wrapcxt, INOUT void **user_data)
{
    char command = 0;
    int i;
    void *drcontext;
    app_pc target_to_fuzz = drwrap_get_func(wrapcxt);
    dr_mcontext_t *mc = drwrap_get_mcontext_ex(wrapcxt, DR_MC_ALL);
    drcontext = drwrap_get_drcontext(wrapcxt);
    // 保存目标函数的 栈指针 和 pc 指针, 以便在执行完程序后回到该状态继续运行
    fuzz_target.xsp = mc->xsp;
    fuzz_target.func_pc = target_to_fuzz;
    if(!options.debug_mode) {
        WriteCommandToPipe('P');
        command = ReadCommandFromPipe();
        // 等待 afl-fuzz 发送 F , 收到 F 开始进行 fuzzing
        if(command != 'F') {
            if(command == 'Q') {
                dr_exit_process(0);
            } else {
                DR_ASSERT_MSG(false, "unrecognized command received over pipe");
            }
        }
    } else {
        debug_data.pre_hanlder_called++;
        dr_fprintf(winafl_data.log, "In pre_fuzz_handler\n");
    }
    //save or restore arguments, 第一次进入时保存参数, 以后都把保存的参数写入
    if (!options.no_loop) {
        if (fuzz_target.iteration == 0) {
            for (i = 0; i < options.num_fuz_args; i++)
                options.func_args[i] = drwrap_get_arg(wrapcxt, i);
        } else {
            for (i = 0; i < options.num_fuz_args; i++)
                drwrap_set_arg(wrapcxt, i, options.func_args[i]);
        }
    }
    memset(winafl_data.afl_area, 0, MAP_SIZE);
    // 把 覆盖率信息保存在 tls 里面, 在统计边覆盖率时会用到
    if(options.coverage_kind == COVERAGE_EDGE || options.thread_coverage) {
        void **thread_data = (void **)drmgr_get_tls_field(drcontext, winafl_tls_field);
        thread_data[0] = 0;
        thread_data[1] = winafl_data.afl_area;
    }
}
  1. 首先保存一些上下文信息,比如寄存器信息,然后通过命名管道像 afl-fuzz 发送 P 表示这边已经准备好了可以执行用例,然后等待 afl-fuzz 发送 F 后,就继续向下执行。
  2. 然后如果是第一次执行,就保存函数的参数,否则就把之前保存的参数设置好。
  3. 然后重置表示代码覆盖率的共享内存区域。

然后在 post_fuzz_handle 会根据执行的情况向 afl-fuzz 返回执行信息,然后根据情况判断是否恢复之前保存的上下文信息,重新准备开始执行目标函数。通过这种方式可以不用每次执行都新建一个进程,提升了 fuzz 的效率。

static void
post_fuzz_handler(void *wrapcxt, void *user_data)
{
    dr_mcontext_t *mc;
    mc = drwrap_get_mcontext(wrapcxt);

    if(!options.debug_mode) {
        WriteCommandToPipe('K');  // 程序正常执行后发送 K 给 fuzz
    } else {
        debug_data.post_handler_called++;
        dr_fprintf(winafl_data.log, "In post_fuzz_handler\n");
    }
    /* 
        We don't need to reload context in case of network-based fuzzing. 
        对于网络型的 fuzz , 不需要reload.执行一次就行了,这里直接返回
    */
    if (options.no_loop)
        return;
    fuzz_target.iteration++;
    if(fuzz_target.iteration == options.fuzz_iterations) {
        dr_exit_process(0);
    }
    // 恢复 栈指针 和 pc 到函数的开头准备下次继续运行
    mc->xsp = fuzz_target.xsp;
    mc->pc = fuzz_target.func_pc;
    drwrap_redirect_execution(wrapcxt);
}

4.4 persistence模式

在 fuzz 网络应用程序时,应该使用该模式-persistence_mode in_app
在这个模式下,对目标函数的包装就没有pre_fuzzpost_fuzz了, 此时就是在每次运行到目标函数就清空覆盖率, 因为程序自身会不断的调用目标函数。

/* 每次执行完就简单的重置 aflmap, 这种模式适用于程序自身就有循环的情况 */
static void
pre_loop_start_handler(void *wrapcxt, INOUT void **user_data)
{
    void *drcontext = drwrap_get_drcontext(wrapcxt);
    if (!options.debug_mode) {
        //let server know we finished a cycle, redundunt on first cycle.
        WriteCommandToPipe('K');
        if (fuzz_target.iteration == options.fuzz_iterations) {
            dr_exit_process(0);
        }
        fuzz_target.iteration++;
        //let server know we are starting a new cycle
        WriteCommandToPipe('P'); 
        //wait for server acknowledgement for cycle start
        char command = ReadCommandFromPipe(); 
        if (command != 'F') {
            if (command == 'Q') {
                dr_exit_process(0);
            }
            else {
                char errorMessage[] = "unrecognized command received over pipe: ";
                errorMessage[sizeof(errorMessage)-2] = command;
                DR_ASSERT_MSG(false, errorMessage);
            }
        }
    }
    else {
        debug_data.pre_hanlder_called++;
        dr_fprintf(winafl_data.log, "In pre_loop_start_handler\n");
    }
    memset(winafl_data.afl_area, 0, MAP_SIZE);
    if (options.coverage_kind == COVERAGE_EDGE || options.thread_coverage) {
        void **thread_data = (void **)drmgr_get_tls_field(drcontext, winafl_tls_field);
        thread_data[0] = 0;
        thread_data[1] = winafl_data.afl_area;
    }
}

通过对 afl-fuzz.c 的分析,我们知道 winafl 提供了两种有意思的接口,即数据修正功能自定义数据发送功能。这两种功能可以辅助我们对一些非常规目标进行 fuzz, 比如网络协议、数据加密应用。通过对 winafl.c 可以清楚的知道如何使用 dynamorio 统计程序的覆盖率, 并且明白了 winafl 通过多次在内存中执行目标函数来提升效率的方式, 同时也清楚了在程序内部自带循环调用函数时,可以使用 persistence 模式来对目标进行 fuzz,比如一些网络服务应用。

5. 一些小的改进

  • dry_run函数中对于一开始加载进来的原始输入,如果产生variable behavior应该将其删去
  • winafl.cdebug模式下,添加比较同一个样本的bitmap,直接判定是否参数variable behavior

5.1 DR里对于basic_block插桩的处理逻辑

DR里面实现对basic_block插桩的API主要是drmgr_register_bb_instrumentation_event这个函数,其定义如下:

DR_EXPORT bool drmgr_register_bb_instrumentation_event ( drmgr_analysis_cb_t  analysis_func,
drmgr_insertion_cb_t  insertion_func,
drmgr_priority_t *  priority 
)  

Registers callback functions for the second and third instrumentation stages: application analysis and instrumentation insertion. drmgr will call func as the second of four instrumentation stages for each dynamic application basic block.
这个API接口用于注册第二个和第三个插桩阶段的回调函数:application analysisinstrumentation insertion。对于每个动态应用程序基本块,drmgr将调用func作为四个检测阶段的第二个阶段。(4个插桩阶段,这里我还不是很懂)

The first stage performed any changes to the original application code, and later stages are not allowed to change application code. Application analysis passes in the second stage are not allowed to add to or change the instruction list other than adding label instructions, and are intended for analysis of application code either for immediate use or for use by the third stage. Label instructions can be used to store data for use in subsequent stages with custom tags inserted as notes via drmgr_reserve_note_range() and custom data stored via instr_get_label_data_area().
第一个阶段可以对原始应用程序代码进行任何更改,而在后面的阶段则不允许更改应用程序代码。除了添加标签指令外,第二阶段的应用程序分析(Application analysis)过程不允许添加或更改指令列表,并且旨在分析应用程序代码以立即使用或由第三阶段使用。标签指令可用于存储数据以供后续阶段使用,其中自定义标签通过drmgr_reserve_note_range()作为注释插入,而自定义数据通过instr_get_label_data_area()存储。

The third instrumentation stage is instrumentation insertion. Unlike the other stages, this one passes only one instruction to the callback, allowing each registered component to act on one instruction before moving to the next instruction. Instrumentation insertion passes are allowed to insert meta instructions only immediately prior to the passed-in instruction: not before any prior non-meta instruction nor after any subsequent non-meta instruction. They are not allowed to insert new non-meta instructions or change existing non-meta instructions. Because other components may have already acted on the instruction list, be sure to ignore already existing meta instructions.
插桩的第三阶段是instrumentation insertion。与其他阶段不同,该阶段仅将一条指令传递给回调,从而允许每个已注册的组件在移至下一条指令之前对一条指令进行操作。允许仪表插入过程仅在传入指令之前插入元指令:不在任何先前的非元指令之前或任何后续的非元指令之后。不允许他们插入新的非元指令或更改现有的非元指令。由于其他组件可能已经作用在指令列表上,因此请确保忽略已经存在的元指令。

analysis_func和insert_func具有相同的优先级。它们的user_data参数可用于将数据从分析阶段传递到插入阶段。

Reserves a thread-local storage (tls) slot for every thread. Returns the index of the slot, which should be passed to drmgr_get_tls_field() and drmgr_set_tls_field(). Returns -1 if there are no more slots available. Each slot is initialized to NULL for each thread and should be properly initialized with drmgr_set_tls_field() in the thread initialization event (see dr_register_thread_init_event()).

Inserts into ilist prior to where meta-instruction(s) to read into the general-purpose full-size register reg from the user-controlled drcontext field for this thread and index. Reads from the same field as drmgr_get_tls_field().

6. 变异策略

变异策略主要分为7种

  • bitflip,按位翻转,1变为0,0变为1
  • byteflip, 按字节翻转
  • arithmetic,整数加/减算术运算
  • interest,把一些特殊内容替换到原文件中
  • dictionary,把自动生成或用户提供的token替换/插入到原文件中
  • havoc,中文意思是“大破坏”,此阶段会对原文件进行大量变异,具体见下文
  • splice,中文意思是“绞接”,此阶段会将两个文件拼接起来得到一个新的文件
    前五项bitflip,byteflip, arithmetic, interest, dictionary是非dumb mode(-d)和主fuzzer(-M)会进行的操作,由于其变异方式没有随机性,所以也称为deterministic fuzzing.
    havoc和splice则存在随机性,是所有状况的fuzzer(是否dumb mode、主从fuzzer)都会执行的变异。

0xff.参考资料


AFL源码分析与编译策略分析
winafl中覆盖率反馈具体实现分析-泉哥
AFL文件变异策略
看雪AFL源码分析