首页
社区
课程
招聘
[原创]AFLNET源码分析(二)
2023-3-4 13:10 7728

[原创]AFLNET源码分析(二)

2023-3-4 13:10
7728

在AFLNET源码分析(一)中,我们主要分析了send_over_network()函数,弄清楚了为什么AFL多用于文件处理类程序而AFLNET却可以应用于网络协议服务端的模糊测试。

 

本篇文章将着眼于模糊测试的整体流程

 

首先从main函数入手,AFLNET与AFL在main函数中相同的部分这里就直接略过不讲了,网上分析AFL源码的文章有很多。

 

直接从main里的fuzz主循环开始看:

 

首先查看是否开启了状态感知模式,我们通过在命令中增加- E 选项就可以开启,如果不开启的话则会走AFL原有的循环逻辑

1
2
3
4
if (state_aware_mode) {
    if (state_ids_count == 0) {
      PFATAL("No server states have been detected. Server responses are likely empty!");
    }

满足if条件则会进入整个fuzzing的主循环

 

在主循环中首先初始化selected_seed为NULL,然后判断种子是否未选择以及选择的种子的region数量是否为0,满足任意一个则进入一个用于选择状态的循环

 

在状态选择的循环中,执行如下逻辑:

  • 调用choose_target_state选取目标状态(这里的state_selection_algo是一个枚举变量,表示选择哪种状态选择策略,默认是轮询选择)
  • 基于已选择的状态调用cull_queue精简队列
  • 更新一个状态被选取过的次数(这里用到的是khash)
  • 调用choose_seed选择种子,其中seed_selection_algo也是一个枚举变量,表示种子选择策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while (1) {
      u8 skipped_fuzz;
 
      struct queue_entry *selected_seed = NULL;
      while(!selected_seed || selected_seed->region_count == 0) {
        target_state_id = choose_target_state(state_selection_algo);
 
        /* Update favorites based on the selected state */
        cull_queue();
 
        /* Update number of times a state has been selected for targeted fuzzing */
        khint_t k = kh_get(hms, khms_states, target_state_id);
        if (k != kh_end(khms_states)) {
          kh_val(khms_states, k)->selected_times++;
        }
 
        selected_seed = choose_seed(target_state_id, seed_selection_algo);
      }

具体AFLNET是如何选择状态和选择种子的我们等到主循环分析结束之后再来详细分析

 

接着往下看

 

如果selected_seed不为NULL,就要在queue里寻找这个种子并将其设置为queue_cur

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (selected_seed) {
        if (!queue_cur) {
            current_entry     = 0;
            cur_skipped_paths = 0;
            queue_cur         = queue;
            queue_cycle++;
        }
        while (queue_cur != selected_seed) {
          queue_cur = queue_cur->next;
          current_entry++;
          if (!queue_cur) {
            current_entry     = 0;
            cur_skipped_paths = 0;
            queue_cur         = queue;
            queue_cycle++;
          }
        }
      }

接下来调用fuzz_one函数进行一轮fuzz,然后根据三个值来判断是否要调用sync_fuzzers

 

其中skipped_fuzz表示是否开启了IGNORE_FINDS模式,如果开启则fuzzer只会使用原始种子文件进行fuzz

 

stop_soon则是由用户的ctrl+c操作进行设置,表示用户是否要停止fuzzing

 

sync_id表示fuzzer 的id

 

如果三个条件都满足,则将sync_interval_cnt加1模5并判断是否为0,为0则调用sync_fuzzers。即在一切正常的情况下每SYNC_INTERVAL(默认为5)次调用一次sync_fuzzers。

1
2
3
4
5
6
7
skipped_fuzz = fuzz_one(use_argv);
      if (!stop_soon && sync_id && !skipped_fuzz) {
        if (!(sync_interval_cnt++ % SYNC_INTERVAL))
          sync_fuzzers(use_argv);
      }
      if (!stop_soon && exit_1) stop_soon = 2;
      if (stop_soon) break;

这里注意到一点,上述代码和AFL中没有区别,少了两句:

1
2
queue_cur = queue_cur->next;
    current_entry++;

这是AFL中有而AFLNET中没有的,并且只是状态感知模式下没有,没开状态感知的话仍然是有这两句代码的。原因在于AFL的fuzzing队列是顺序往下走的,但是状态感知模式会在开头通过选择状态和种子来确定本次fuzzing所用的queue是什么

 

后面的代码AFLNET和AFL就没有任何区别了,都是一些信息展示和退出的功能,这里就不再赘述。

 

接下来分析在开头暂时略过的一些函数

 

choose_target_state:

 

函数接收一个单字节参数,表示选用哪种状态选择策略,默认为2轮询模式,不过官方给的例子用的是3偏好模式(也许翻译的有偏差)

 

如果是第一种随机模式,则selected_state_index直接从UR(即random函数)中获取,范围用state_ids_count来限制。

 

如果是第二种轮询模式,则selected_state_index不断加一按顺序进行轮转,如果等于状态总数了就回到0继续下一轮

 

如果是第三种偏好模式,在state_cycles小于5的时候会首先按照轮询的模式进行选择,因为需要先获取足够的状态信息,如果大于5则调用update_scores_and_select_next_state获取result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
unsigned int choose_target_state(u8 mode) {
  u32 result = 0;
  switch (mode) {
    case RANDOM_SELECTION: //Random state selection
      selected_state_index = UR(state_ids_count);
      result = state_ids[selected_state_index];
      break;
    case ROUND_ROBIN: //Round-robin state selection
      result = state_ids[selected_state_index];
      selected_state_index++;
      if (selected_state_index == state_ids_count) selected_state_index = 0;
      break;
    case FAVOR:
      /* Do ROUND_ROBIN for a few cycles to get enough statistical information*/
      if (state_cycles < 5) {
        result = state_ids[selected_state_index];
        selected_state_index++;
        if (selected_state_index == state_ids_count) {
          selected_state_index = 0;
          state_cycles++;
        }
        break;
      }
      result = update_scores_and_select_next_state(FAVOR);
      break;
    default:
      break;
  }
  return result;
}

继续深入update_scores_and_select_next_state看看其逻辑:

 

由于下面的代码中涉及到了一些state的属性,所以我们先来熟悉一下state结构(注释已进行标注)

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
  u32 id;                     /* 状态id */
  u8 is_covered;              /* 状态是否被覆盖 */
  u32 paths;                  /* 此状态经过的所有路径 */
  u32 paths_discovered;       /* 由此状态新发现的路径*/
  u32 selected_times;         /* 此状态被选择过的次数*/
  u32 fuzzs;                  /* fuzz总次数 */
  u32 score;                  /* 状态分数 */
  u32 selected_seed_index;    /* 最近选择过此状态的种子序号*/
  void **seeds;               /* 保存了所有能到达此状态的种子* */
  u32 seeds_count;            /* 能到达此状态的种子数量 */
} state_info_t;

如果state_ids_count为0则直接返回0,为state_scores申请内存,每个state申请4字节

 

然后更新每个状态的分数,更新的时候会依据fuzz的次数,被选择过的次数以及发现路径的条数这些指标来计算分数,分数越高说明这个状态越好。

 

接下来的逻辑一眼看上去可能会有些奇怪,按照惯性思维我们应该将分数排序然后选取最高的,但是作者采用的方式是将得分累计起来,然后在所有分数之和的范围内获取一个随机数。

 

然后调用index_search来获取最终的idx,这个index_search的实现也较为简单,就是找到第一个大于上一步获取的随机数的分数,将它的序号作为最后选出的idx

 

这么实现的道理是,保证了分数越高,被随机数命中的概率就越大,从而实现分数越高越容易选中,同时省去了排序操作效率更高,并且兼顾了随机性,还是比较巧妙的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
u32 update_scores_and_select_next_state(u8 mode) {
  u32 result = 0, i;
  if (state_ids_count == 0) return 0;
  u32 *state_scores = NULL;
  state_scores = (u32 *)ck_alloc(state_ids_count * sizeof(u32));
  if (!state_scores) PFATAL("Cannot allocate memory for state_scores");
  khint_t k;
  state_info_t *state;
  //Update the states' score
  for(i = 0; i < state_ids_count; i++) {
    u32 state_id = state_ids[i];
    k = kh_get(hms, khms_states, state_id);
    if (k != kh_end(khms_states)) {
      state = kh_val(khms_states, k);
      switch(mode) {
        case FAVOR:
          state->score = ceil(1000 * pow(2, -log10(log10(state->fuzzs + 1) * state->selected_times + 1)) * pow(2, log(state->paths_discovered + 1)));
          break;
        //other cases are reserved
      }
      if (i == 0) {
        state_scores[i] = state->score;
      } else {
        state_scores[i] = state_scores[i-1] + state->score;
      }
    }
  }
  u32 randV = UR(state_scores[state_ids_count - 1]);
  u32 idx = index_search(state_scores, state_ids_count, randV);
  result = state_ids[idx];
  if (state_scores) ck_free(state_scores);
  return result;
}

然后再来看看choose_seed,由于代码较长这里分开叙述

 

首先通过传入的状态id将之前选出的状态找到

1
2
3
4
5
6
7
khint_t k;
  state_info_t *state;
  struct queue_entry *result = NULL;
  k = kh_get(hms, khms_states, target_state_id);
  if (k != kh_end(khms_states)) {
    state = kh_val(khms_states, k);
    if (state->seeds_count == 0) return NULL;

随机模式与轮询模式

 

随机模式同样以种子数量为范围获取一个随机数然后赋给result,轮询模式也和之前状态选择策略差不多

1
2
3
4
5
6
7
8
9
case RANDOM_SELECTION: //Random seed selection
        state->selected_seed_index = UR(state->seeds_count);
        result = state->seeds[state->selected_seed_index];
        break;
      case ROUND_ROBIN: //Round-robin seed selection
        result = state->seeds[state->selected_seed_index];
        state->selected_seed_index++;
        if (state->selected_seed_index == state->seeds_count) state->selected_seed_index = 0;
        break;

偏好模式:

 

如果种子数量小于10则按照轮询模式的逻辑来,如果大于是则进入以下逻辑

 

以下逻辑总体来讲就是为不同条件的种子设置了不同的概率去选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
if (state->seeds_count > 10) {
          //Do seed selection similar to AFL + take into account state-aware information
          //e.g., was_fuzzed information becomes state-aware
          u32 passed_cycles = 0;
          while (passed_cycles < 5) {
            result = state->seeds[state->selected_seed_index];
            if (state->selected_seed_index + 1 == state->seeds_count) {
              state->selected_seed_index = 0;
              passed_cycles++;
            } else state->selected_seed_index++;
            //Skip this seed with high probability if it is neither an initial seed nor a seed generated while the
            //current target_state_id was targeted
            if (result->generating_state_id != target_state_id && !result->is_initial_seed && UR(100) < 90) continue;
            u32 target_state_index = get_state_index(target_state_id);
            if (pending_favored) {
              /* If we have any favored, non-fuzzed new arrivals in the queue,
                 possibly skip to them at the expense of already-fuzzed or non-favored
                 cases. */
              if (((was_fuzzed_map[target_state_index][result->index] == 1) || !result->favored) && UR(100) < SKIP_TO_NEW_PROB) continue;
              /* Otherwise, this seed is selected */
              break;
            } else if (!result->favored && queued_paths > 10) {
              /* Otherwise, still possibly skip non-favored cases, albeit less often.
                 The odds of skipping stuff are higher for already-fuzzed inputs and
                 lower for never-fuzzed entries. */
              if (queue_cycle > 1 && (was_fuzzed_map[target_state_index][result->index] == 0)) {
                if (UR(100) < SKIP_NFAV_NEW_PROB) continue;
              } else {
                if (UR(100) < SKIP_NFAV_OLD_PROB) continue;
              }
              /* Otherwise, this seed is selected */
              break;
            }
          }
        }

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2023-3-30 08:48 被Ayakaaa编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 2264
活跃值: (1508)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
CatF1y 2 2023-3-4 13:15
2
0
tql
游客
登录 | 注册 方可回帖
返回