Coverage-based Greybox Fuzzing as Markov Chain

AFLFast: Coverage-based Greybox Fuzzing as Markov Chain

一、论文阅读

论文来自CCS2016

作者:Marcel Böhme 模糊测试领域巨佬

Abstract

基于覆盖的灰盒模糊测试 Coverage-based Greybox Fuzzing (CGF)。大多数测试用例执行少数高频路径,制定策略倾向于低频路径,使用马尔科夫链模型解释了CGF,该模型指出了执行路径 i 的种子产生覆盖路径 j 的测试用例的概率。每个种子都有能量,即从该种子产生的测试用例数量。如果每次被选择的种子的能量与固定分布的密度成反比并且单调递增

1. Introduction

  • 利用马尔科夫链模型解释了AFL的挑战以及AFLFast的卓越性。
  • 引入并评估了几种控制种子产生测试用例数量的策略,在相同时间内执行更多低频路径
  • 设计并评估了几种控制种子选择的策略
  • 实现了AFLFast并开源 https://github.com/mboehme/aflfast

2. Background

2.1 Covera-based Greybox Fuzzing
2.2 Markov Chain

https://blog.csdn.net/weixin_42437114/article/details/120935992

马尔可夫链是从从一个状态转换为另一个状态的随机过程,在任意时间,只能处于一个状态。状态集合被称为状态空间。从一个状态转移到另一个状态的概率被称为转移概率,转移概率只和前一状态有关。

3. Markov Chain Model

作者认为模糊器如果专注于马尔可夫链的低密度区域将会执行更多路径。所以作者的目标是更多访问位于平稳分布中的低密度区域的状态,更少访问位于平稳分布中的高密度区域的状态。

3.1 Coverage-based Fuzzing as Markov Chain

模糊器发现路径的序列可以由马尔可夫链描述,转移概率 p i , j p_{i,j} pi,j 为执行路径i的种子变异出执行路径j的测试用例的概率,但不是时间齐次的,即不同时间 p i , j p_{i,j} pi,j可能不同。这不能保证平稳分布存在。

时间齐次模型。给定种子集T,S+为种子发现的路径集合,S-为测试用例发现路径集合。马尔可夫链的状态空间为 S = S + ⋃ S − S=S^+ \bigcup S^- S=S+S

转移概率 p i , j p_{i,j} pi,j 定义如下:如果 i ∈ S + i \in S^+ iS+ p i , j p_{i,j} pi,j 为执行路径i的种子变异出执行路径j的测试用例的概率;如果 i ∈ S − i \in S^- iS ,则 p i , i = 1 − ∑ t j ∈ T p j , i p_{i,i}=1-\sum_{t_j\in T}p_{j,i} pi,i=1tjTpj,i ,并且对于所有的 t j ∈ T t_j\in T tjT p i , j = p j , i p_{i,j}=p_{j,i} pi,j=pj,i。即作者做了两个假设:执行路径i的种子变异出执行路径j的测试用例的概率和执行路径j的种子变异出执行路径i的测试用例的概率相同; i ∈ S − i \in S^- iS没有其它未被发现的邻居。

马尔可夫链的平稳分布 π \pi π简言之就是当步数N(应该指转移次数)趋于无限大时,停留在状态i占比。占比大于算术平均值为高密度区域,低于算术平均值为低密度区域。

能量为一个种子生成的测试用例数量,某个状态的能量由预先定义的能量调度决定。

在这里插入图片描述

10%的路径有1000至100000个测试用例执行,而30%的路径只有一个测试用例执行。

这样的马尔可夫链是快速收敛的,根据平稳分布,不论初始时是哪一类种子都将有更高的概率执行高频路径,

3.2 Running Example
void crashme(char* s){
    if (s[0] == ’b’)
        if (s[1] == ’a’)
            if (s[2] == ’d’)
                if (s[3] ==!)
                    abort();
}

上面的代码有五条路径,马尔可夫链如下:

在这里插入图片描述

每次变异一个字符,选择某个字符的概率为1/4(2-2),变异为28个字符集中的一个,变异成某个字符的概率为2-8。剩下的就很好理解了。

3.3 Challenges of Coverage-based Fuzzers

在这里插入图片描述

上图AFL的能量分配示例,假设从字符串“ball”开始,每个种子的能量为 29

则根据上面的状态转移概率,将会有1/4的概率执行****即27,1/4的概率执行b***,即27,1/2的概率执行ba**,即2·27。根据AFL的种子选择策略,按照添加进队列的顺序选择种子进行fuzz。接下来分别fuzz **** 和 b***,则执行各个路径的能量累加即为上图第二行和第三行。

4. Boosting Greybox Fuzzing

文中提出了单调的能量调度方案,首先为种子分配较低的能量,随后当每个种子被选择时单调递增。此外,能量分配和平稳分配的密度成反比。

为了分配更多能量给低密度区的状态,设计了种子选择策略,优先选择最少被选择的种子或是最少被测试用例执行的种子。

4.1 Power Schedules

p ( i ) p(i) p(i) fuzz执行路径i的种子t时分配给其的能量, p ( i ) p(i) p(i)是关于种子被选择次数 s ( i ) s(i) s(i)和执行路径i的测试用例数量 f ( i ) f(i) f(i)的函数。事实上 f ( i ) f(i) f(i)用作分布密度的近似值。

  • exploer模式

p ( i ) = α ( i ) β p(i)={\alpha (i) \over \beta} p(i)=βα(i)

  • coe模式,不fuzz高频路径,只fuzz低频路径,慢慢地高频路径会变为低频路径

p ( i ) = { 0 , i f   f ( i ) > μ m i n ( α ( i ) β ⋅ 2 s ( i ) , M ) , o t h e r w i s e p(i)= \begin{cases} 0, & if \ f(i)>\mu\\ min({\alpha (i) \over \beta}·2^{s(i)}, M), & otherwise \end{cases} p(i)={0,min(βα(i)2s(i),M),if f(i)>μotherwise

其中 μ \mu μ是队列中所有种子的执行路径的平均执行频率,M 为上限
μ = ∑ i ∈ S + f ( i ) ∣ S + ∣ \mu={\sum_{i \in S^+ }f(i) \over \lvert S^+\rvert} μ=S+iS+f(i)

  • fast模式,coe模式的扩展,小于 μ \mu μ 时,能量与 f ( i ) f(i) f(i) 成反比

p ( i ) = m i n ( α ( i ) β ⋅ 2 s ( i ) f ( i ) , M ) p(i)={min({\alpha (i) \over \beta}·{2^{s(i)} \over f(i)}}, M) p(i)=min(βα(i)f(i)2s(i),M)

  • linear模式,与fast模式类似,能量由指数增长变为线性增长

p ( i ) = m i n ( α ( i ) β ⋅ s ( i ) f ( i ) , M ) p(i)={min({\alpha (i) \over \beta}·{s(i) \over f(i)}}, M) p(i)=min(βα(i)f(i)s(i),M)

  • quad模式,平方

x p ( i ) = m i n ( α ( i ) β ⋅ s ( i ) 2 f ( i ) , M ) xp(i)={min({\alpha (i) \over \beta}·{s(i)^2 \over f(i)}}, M) xp(i)=min(βα(i)f(i)s(i)2,M)

4.2 Search Strategies

s(i)优先,被fuzz次数少的种子优先

f(i)优先,执行路径i的测试用例数量少的优先

4.3 Implementation of AFLFast

AFL根据种子的执行速度、覆盖以及生成时间决定分配的能量,AFLFast的五种模式中都保持了这一策略。

AFL中,第一次被fuzz的种子执行确定性变异,而AFLFast一开始分配给种子的能量较低,所以当分配的能量符合确定性变异需要的能量时才执行确定性变异。

此外,AFL首先计算出种子的能量,在本轮fuzz该种子的过程中如果发现了interesting的测试用例,会增加该种子本轮的能量,AFLFast禁用了该功能。

在标记favored时,对于每一条边,AFL选择最小最快的。AFLFast首先标记覆盖这条边并且被fuzz次数最少的种子(源码中未体现),如果存在多个,则标记执行了 被最少数量的测试用例执行的路径f(i) 的种子,如果仍然有多个,才选择最小最快的。

在种子选择时,AFL中离当前种子最近的下一个favored种子将会被选择(即按照队列中的顺序)。AFLFast中选择s(i)最小的,如果有多个,选择f(i) 最小的。(源码中未体现)

5. Evaluation

作者根据cksum判断路径是否相同,并实现了一个哈希表(cksum(i), f(i)),

分别讨论了每种能量分配模式以及每种种子选择策略的效果

总的来说,fast模式的调度策略效果最好。

二、源码分析

1. 新增参数选项

分别对应五种能量调度模式,第三个是AFL默认的能量调度

  • -p fast
  • -p coe
  • -p exploit
  • -p lin
  • -p quad
  • -p explore

2. 一些变量

fuzz_level:种子被fuzz的次数,即 s(i)

n_fuzz:该种子执行路径的执行频率(换言之,该种子执行路径被多少测试用例执行过)

3. calculate_score

static u32 calculate_score(struct queue_entry* q) {

  u32 avg_exec_us = total_cal_us / total_cal_cycles;
  u32 avg_bitmap_size = total_bitmap_size / total_bitmap_entries;
  u32 perf_score = 100;

  /* Adjust score based on execution speed of this path, compared to the
     global average. Multiplier ranges from 0.1x to 3x. Fast inputs are
     less expensive to fuzz, so we're giving them more air time. */

  if (q->exec_us * 0.1 > avg_exec_us) perf_score = 10;
  else if (q->exec_us * 0.25 > avg_exec_us) perf_score = 25;
  else if (q->exec_us * 0.5 > avg_exec_us) perf_score = 50;
  else if (q->exec_us * 0.75 > avg_exec_us) perf_score = 75;
  else if (q->exec_us * 4 < avg_exec_us) perf_score = 300;
  else if (q->exec_us * 3 < avg_exec_us) perf_score = 200;
  else if (q->exec_us * 2 < avg_exec_us) perf_score = 150;

  /* Adjust score based on bitmap size. The working theory is that better
     coverage translates to better targets. Multiplier from 0.25x to 3x. */

  if (q->bitmap_size * 0.3 > avg_bitmap_size) perf_score *= 3;
  else if (q->bitmap_size * 0.5 > avg_bitmap_size) perf_score *= 2;
  else if (q->bitmap_size * 0.75 > avg_bitmap_size) perf_score *= 1.5;
  else if (q->bitmap_size * 3 < avg_bitmap_size) perf_score *= 0.25;
  else if (q->bitmap_size * 2 < avg_bitmap_size) perf_score *= 0.5;
  else if (q->bitmap_size * 1.5 < avg_bitmap_size) perf_score *= 0.75;

  /* Adjust score based on handicap. Handicap is proportional to how late
     in the game we learned about this path. Latecomers are allowed to run
     for a bit longer until they catch up with the rest. */

  if (q->handicap >= 4) {

    perf_score *= 4;
    q->handicap -= 4;

  } else if (q->handicap) {

    perf_score *= 2;
    q->handicap--;

  }

  /* Final adjustment based on input depth, under the assumption that fuzzing
     deeper test cases is more likely to reveal stuff that can't be
     discovered with traditional fuzzers. */

  switch (q->depth) {

    case 0 ... 3:   break;
    case 4 ... 7:   perf_score *= 2; break;
    case 8 ... 13:  perf_score *= 3; break;
    case 14 ... 25: perf_score *= 4; break;
    default:        perf_score *= 5;

  }
    
  // 以上为AFL默认能量调度策略,即论文中的 alpha

  u64 fuzz = q->n_fuzz;	// 路径频率
  u64 fuzz_total;	

  u32 n_paths, fuzz_mu;
  u32 factor = 1;	// 公式中的右半部分

  switch (schedule) {

    case EXPLORE: 
      break;

    case EXPLOIT:
      factor = MAX_FACTOR;	// 默认策略
      break;

    case COE:
      fuzz_total = 0;
      n_paths = 0;

      struct queue_entry *queue_it = queue;	
      while (queue_it) {	// 统计所有种子的路径执行频率,这里不会重复统计,因为加入队列的种子都是执行不同路径的
        fuzz_total += queue_it->n_fuzz;
        n_paths ++;	// 种子数量
        queue_it = queue_it->next;
      }

      fuzz_mu = fuzz_total / n_paths;	// 求mu
      if (fuzz <= fuzz_mu) {	// 低频路径
        if (q->fuzz_level < 16)
          factor = ((u32) (1 << q->fuzz_level));
        else 
          factor = MAX_FACTOR;	// MAX_FACTOR 即论文中的M
      } else {	// 高频路径
        factor = 0;
      }
      break;
    
    case FAST:
      if (q->fuzz_level < 16) {
         factor = ((u32) (1 << q->fuzz_level)) / (fuzz == 0 ? 1 : fuzz); 
      } else
        factor = MAX_FACTOR / (fuzz == 0 ? 1 : next_p2 (fuzz));
      break;

    case LIN:	// 线性
      factor = q->fuzz_level / (fuzz == 0 ? 1 : fuzz); 
      break;

    case QUAD:	// 平方
      factor = q->fuzz_level * q->fuzz_level / (fuzz == 0 ? 1 : fuzz);
      break;

    default:
      PFATAL ("Unkown Power Schedule");
  }
  if (factor > MAX_FACTOR) 
    factor = MAX_FACTOR;	// MAX_FACTOR = 32

  perf_score *= factor / POWER_BETA;	// POWER_BETA = 1

  /* Make sure that we don't go over limit. */

  if (perf_score > HAVOC_MAX_MULT * 100) perf_score = HAVOC_MAX_MULT * 100;

  return perf_score;

}

4. save_if_interesting 中的更改

  // 每个测试用例执行完后都计算表示路径的校验和,然后将队列中执行相同路径的种子的路径频率+1
  /* Update path frequency. */
  u32 cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);

  struct queue_entry* q = queue;
  while (q) {
    if (q->exec_cksum == cksum)
      q->n_fuzz = q->n_fuzz + 1;

    q = q->next;

  }

5. update_bitmap_score

static void update_bitmap_score(struct queue_entry* q) {

  u32 i;
  u64 fuzz_p2      = next_p2 (q->n_fuzz);	// 求大于等于n_fuzz的第一个2的次方
  u64 fav_factor = q->exec_us * q->len;

  /* For every byte set in trace_bits[], see if there is a previous winner,
     and how it compares to us. */

  for (i = 0; i < MAP_SIZE; i++)

    if (trace_bits[i]) {	// 对于每一个覆盖到的边

       if (top_rated[i]) {	// 如果存在最优者

         u64 top_rated_fuzz_p2    = next_p2 (top_rated[i]->n_fuzz);
         u64 top_rated_fav_factor = top_rated[i]->exec_us * top_rated[i]->len;
		 // 如果执行频率大于原有最优着,则跳过
         if (fuzz_p2 > top_rated_fuzz_p2) continue;
         else if (fuzz_p2 == top_rated_fuzz_p2) {	// 如果等于

           if (fav_factor > top_rated_fav_factor) continue;	// 根据速度和大小决定去留

         }

         /* Looks like we're going to win. Decrease ref count for the
            previous winner, discard its trace_bits[] if necessary. */

         if (!--top_rated[i]->tc_ref) {
           ck_free(top_rated[i]->trace_mini);
           top_rated[i]->trace_mini = 0;
         }

       }

       /* Insert ourselves as the new winner. */

       top_rated[i] = q;
       q->tc_ref++;

       if (!q->trace_mini) {
         q->trace_mini = ck_alloc(MAP_SIZE >> 3);
         minimize_bits(q->trace_mini, trace_bits);
       }

       score_changed = 1;

     }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值