主要功能与整体过程
1:主要功能是从queue中取出entry进行fuzz,成功返回0,跳过或退出的话返回1,关键功能是变异。
- SIMPLE BITFLIP (+dictionary construction)阶段
- ARITHMETIC INC/DEC 阶段
- INTERESTING VALUES阶段
- DICTIONARY STUFF阶段
- RANDOM HAVOC阶段
- SPLICING阶段
2:整体流程
- 根据是否有 pending_favored(在cull_queue函数中定义,暂时猜测为待定的感兴趣队列) 和queue_cur的情况,按照概率进行跳过;有pending_favored, 对于已经fuzz过的或者non-favored的有99%的概率跳过;无pending_favored,95%跳过fuzzed&non-favored,75%跳过not fuzzed&non-favored,不跳过favored;
- 假如当前项有校准错误,并且校准错误次数小于3次,那么就用calibrate_case进行测试;
- 如果测试用例没有修剪过,那么调用函数trim_case对测试用例进行修剪;
- 修剪完毕之后,使用calculate_score对每个测试用例进行打分;
- 如果该queue已经完成deterministic阶段,则直接跳到havoc阶段;
- deterministic阶段变异4个阶段,变异过程中会多次调用函数common_fuzz_stuff函数,保存interesting 的种子:
- bitflip,按位翻转,1变为0,0变为1
- arithmetic,整数加/减算术运算
- interest,把一些特殊内容替换到原文件中
- dictionary,把自动生成或用户提供的token替换/插入到原文件中
- 非确定性变异两个阶段
- havoc,中文意思是“大破坏”,此阶段会对原文件进行大量变异。
- splice,中文意思是“绞接”,此阶段会将两个文件拼接起来得到一个新的文件。
- 一轮结束
开始源码阅读
1:变量定义
s32 len, fd, temp_len, i, j;
u8 *in_buf, *out_buf, *orig_in, *ex_tmp, *eff_map = 0;
u64 havoc_queued, orig_hit_cnt, new_hit_cnt;
u32 splice_cycle = 0, perf_score = 100, orig_perf, prev_cksum, eff_cnt = 1;
u8 ret_val = 1, doing_det = 0;
u8 a_collect[MAX_AUTO_EXTRA];
u32 a_len = 0;
其中:
- eff_map的类型是u8,在后面的注释中如果出现Effector map,说的就是这个变量。主要的作用就是标记,标记当前byte对应的map块是否需要进行阶段变异。如果是0意味着不需要变异,非0(比如1)就需要变异,比如一开始分析的 arithmetic 8/8 阶段就用过这个eff_map,此时已经它在bitflip阶段经过了改变了。
2:首先判断是否是状态感知模式,如果是跳到AFLNET_REGIONS_SELECTION,本文按代码顺序阅读
3:概率判断
- 判断pending_favored是否不为0,如果不为
- 则对于已经fuzz过的queue(queue_cur->was_fuzzed = = 1)或者不是favored的queue(queue_cur->favored != 1),则会进行UR是否小于SKIP_TO_NEW_PROB这个判断。
- UR(100)的作用是生成一个0~99之间的随机数。
- KIP_TO_NEW_PROB的值为99.
- 0~99之间的数小于99的概率为99%,所以这里的判断有99%的概率为1。
- 所以这三个连续判断的意思就是,对于已经fuzz过的queue(queue_cur->was_fuzzed = = 1)或者不是favored的queue(queue_cur->favored != 1),有99%的概率会直接return 1,相当于跳过这些queue。
- 则对于已经fuzz过的queue(queue_cur->was_fuzzed = = 1)或者不是favored的queue(queue_cur->favored != 1),则会进行UR是否小于SKIP_TO_NEW_PROB这个判断。
- 如果pending_favored为0,且!dumb_mode && !queue_cur->favored && queued_paths > 10(queued_paths是队列中测试案例的数量),则
- rug 于queue_cycle大于1(循环次数是否大于1),以及queue_cur->was_fuzzed为0(当前queue是否已经被fuzz过)的判断。如果满足这两个条件,则说明这个queue是新的,则有75%的概率跳过该queue。
- 说明这个是旧的,有95%跳过
4:调试信息: 如果不是在tty模式下,则输出提示信息并撒互信stdout缓冲区:、
5:AFLNET与AFL对变异的策略出现差异,先复习AFLNET使用具有协议感知能力的变异算子增强fuzz_one函数
给定状态 s 和消息序列 M,AFLNET 通过变异生成一个新序列M’。为了保证变异的序列仍然行使选择的状态 s,AFLNET 将原始序列 M 拆分为三部分:
1)前缀M1需要能够达到选定状态 s
2)候选子序列M2包含所有可以在 M2之后执行但仍保留在 s 中的消息
3)后缀M3就是剩余的子序列.
4)也即<M1,M2,M3>=M.变异消息序列为M’=<M1,mutate(M2),M3>.
通过保持原始子序列M1,M’仍将达到状态 s,这是模糊测试工具当前关注的状态。变异的候选子序列mutate(M2) 在选择的状态 s 上产生一个可选的消息序列。在我们最初的实验中,我们观察到替代请求可能“现在”不可观察,但会传播到以后的响应(, we observed that the alternative requests may not be observable “now”, but propagate to later responses)。因此,AFLNET 继续执行后缀M3.
优点:
1)基于突变的方法可以在没有目标协议规范的前提下利用真实网络流量的有效trace来生成可能有效的新序列。相比而言,基于生成的方法需要详细的协议规范,包括具体的消息模板和协议状态机。
2)基于突变的方法可能进化出特别有趣的消息序列的语料库。引起发现新状态、状态转换或者程序分支的生成序列,会被添加到语料库进行进一步的模糊测试
AFLNET_REGIONS_SELECTION
6:找到M2
- 初始化两个变量记录M2消息序列区域
- M2_start_region_ID是M2消息序列开始的地方
- M2_region_count是M2消息序列的长度
- 如果在状态感知模式下,要进行M2区域的划分
- 没有区域,返回提示信息
- 没有前缀序列:M2ID=0,M2_region_count等于queue_cur->regions中等于第一个queue_cur->regions[0]状态数的个数。要求变异后都能到达第一个的状态
- 有前缀序列,即有M1
- 确定M2_start_region_ID:挨个区域找,直至找到某个区域消息的状态数=target_state_id,M2_start_region_ID++;
- 确定M2_region_count:与上面类似
- 如果不在状态感知模式下,随机选择M2区域
- UR是产生0-(参数-1)内的随机数
- 然后通过construct_kl_messages构造kl_message(保存消息序列)
- 然后按照M2区域的开始位置和长度对消息序列进行划分。
- 找到M2区域的边界,并用M2_prev指向M2区域开始位置的前一个位置,M2_next指向M2区域结束位置的后一个位置。
- kliter_t(lms)it是 M2_prev和M2_next之间为M2子消息序列
- 构造缓冲区
- 首先将M2_pre赋值给it,然后通过while循环,遍历整个M2区域。然后为变异做好准备,构建好缓冲区,并更新out_buf。之后会对out_buf里的东西进行变异。
- 记录缓冲区长度,保证后期变异正确:
PERFORMANCE SCORE
1:首先调用calculate_score函数,计算得分:
- 对于以下三种情况,会直接跳转到havoc_stage去运行:
- skip_deterministic不为0,意思是跳过deterministic阶段。
- queue_cur->was_fuzzed不为0,意思是当前queue被fuzz过了。
- queue_cur->passed_det不为0,意思是当前queue执行过deterministic阶段。
- 如果exec路径校验和使确定性变异超出此主实例的范围,则跳过确定性模糊。
- 如果不跳过deterministic 变异阶段,则设置doing_det为1,表示正在进行deterministic 变异。
Deterministic fuzz阶段
包含若干子阶段
SIMPLE BITFLIP(+dictionary construction)
1:比特翻转变异,拿到一个原始文件,打头阵的就是bitflip,而且还会根据翻转量/步长进行多种不同的翻转,按照顺序依次为:
- bitflip 1/1,每次翻转1个bit,按照每1个bit的步长从头开始
- bitflip 2/1,每次翻转相邻的2个bit,按照每1个bit的步长从头开始
- bitflip 4/1,每次翻转相邻的4个bit,按照每1个bit的步长从头开始
- bitflip 8/8,每次翻转相邻的8个bit,按照每8个bit的步长从头开始,即依次对每个byte做翻转
- bitflip 16/8,每次翻转相邻的16个bit,按照每8个bit的步长从头开始,即依次对每个word做翻转
- bitflip 32/8,每次翻转相邻的32个bit,按照每8个bit的步长从头开始,即依次对每个dword做翻转
2:比特翻转实现-FLIP_BIT
#define FLIP_BIT(_ar, _b) do { \
u8* _arf = (u8*)(_ar); \
u32 _bf = (_b); \
_arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \
} while (0)
- 等式右边:
- _bf & 7,会得到0~7之间的一个整数。
- 十进制128转换成二进制也就是10000000,总共八位
- 128 >> ((_bf) & 7),也就是将10000000右移0~7位,具体是多少位要看_bf这个值是多少
- 等式左边:
- _arf是一个byte大小的指针,初始化时指向_ar的第一个字节
- _arf[i]大小为一个byte,就是8个bit。所以应把_bf >> 3,看作除以8。stage_cur这个数,也就是_b,从0到7除以8等于0,8到15除以8等于1,16到23除以8等于2…依次类推。也就是说,前八次调用FLIP_BIT这个宏时,是_arf[0],也就是第0个字节,后八次调用FLIP_BIT这个宏时,是_arf[1],再往后亦然,达到了一个循环遍历_ar数组的目的。*(这里是比较巧妙的)
- 等式整体:
- 等式左边是为了控制指向的字节数。等式右边相当于提供掩码,其值会从10000000变化到010000000,然后一步一步变为000000001,目的是为了控制该字节翻转的bit位置。然后通过异或运算,达到指定bit位的翻转。
- 总结一下,_ar是操作对象,_br指明操作第几个字节(_bf) >> 3中的第几个bit(128 >> ((_bf) & 7))(从高位到低位)。一个异或相当于实现了对一个指定bit位的翻转这个宏起到的作用就是,对_ar指向的数组的每个字节的每个bit进行翻转。为了优化代码,用的运算全是位运算,所以比较难以理解。
3:stage_name = “bitflip 1/1”
- 下面有段注释,意思是说在进行为翻转的时候,程序会随时注意翻转之后的变化。比如说,对于一段 xxxxxxxxIHDRxxxxxxxx 的文件字符串,当改变 IHDR 任意一个都会导致奇怪的变化,这个时候,程序就会认为 IHDR 是一个可以让fuzzer很激动的“神仙值”–token
- 在进行bitflip 1/1变异时,对于每个byte的最低位(least significant bit)翻转还进行了额外的处理:如果连续多个bytes的最低位被翻转后,程序的执行路径都未变化,而且与原始执行路径不一致,那么就把这一段连续的bytes判断是一条token。
- 比如对于SQL的SELECT ,如果SELECT被破坏,则肯定和正确的路径不一致,而被破坏之后的路径却肯定是一样的,比如AELECT和SBLECT,显然都是无意义的,而只有不破坏token,才有可能出现和原始执行路径一样的结果,所以AFL在这里就是在猜解关键字token。
- 通过循环完成对FLIP_BIT的反复调用
- 因为我们前面是把M2区域单独提出来,放在一个缓冲区内进行变异,并没有对testcase本身进行任何改变。所以一次变异执行完之后,会调用common_fuzz_stuff,编写修改后的testcase,然后再运行一次程序,处理结果。用户可以通过SIGUSR1信号,放弃当前输入,直接goto abandon_entry.
- common_fuzz_stuff(char** argv, u8* out_buf, u32 len),用新的case运行程序,获取fault,检测fault值
- 如果FAULT_TMOUT并且subseq_tmouts(fuzz每个case时置零)未超出限制,返回1
- 若不是FAULT_TMOUT,subseq_tmouts=0
- 用户要求进程终止,返回1
- save_if_interesting()
- 返回0
- common_fuzz_stuff(char** argv, u8* out_buf, u32 len),用新的case运行程序,获取fault,检测fault值
- FLIP_BIT(out_buf, stage_cur);再调用一次将异或翻转过来
- token猜解
- 假设有一段字符串,为xxxxxxxxTCP************。x和可以是任何字符,如果改变了xxxxxxxxx或者*********这两段字符串,都不会造成程序执行流的变化,而改变TCP这个字符串中的任何一个字符,都会导致程序执行流的变化,那么就将TCP定义为一个tokon
- MIN_AUTO_EXTRA =3 MAX_AUTO_EXTRA=32
- 所以先执行else if 的a_len = 0; prev_cksum = cksum;
- 再执行if (cksum != queue_cur->exec_cksum) 进行a_len的累加,直到else if (cksum != prev_cksum) ,预示Token找全了,出现新的路径
- 或者到达了文件结尾:if (stage_cur = = stage_max - 1 && cksum == prev_cksum)
- 猜解token的目的是为了保证关键字符串不被破坏,比如TCP,改变为TAP,肯定会造成错误。
- 从注释上理解token得概念:如果在某一段连续bit上进行连续翻转后,都能让程序产生新的路径,就称连续翻转的这些bit为一个token
- maybe_add_auto函数解释:用于添加token
- 通过MAX_AUTO_EXTRAS和USE_AUTO_EXTRAS两个标志符决定是否采取自动的字典
- 通过异或运算,跳过相同mem。(如果两个mem从头到尾都一样,则最终i会等于len,直接return,起到了跳过相同mem的作用)
- 如果len的长度为2,就和interesting_16数组里的元素比较,如果和其中某一个相同,就直接return。
- 如果len的长度为4,就和interesting_32数组里的元素比较,如果和其中某一个相同,就直接return。
- 这两部分相当于是在筛选token,如果某一候选token在interesting数组里已经存在了,那么就淘汰掉这个候选token。
- 继续筛选token,首先利用extras里的元素都是按照size大小排序的这个特性来优化算法,用一个循环找到大小跟候选token一样的tokens,然后用第二个循环比较extras里是否存在跟候选token一样的token,如果有,候选token就被淘汰。
- 如果通过了上面的筛选,则会设置auto_changed = 1,然后进行跟a_extras的比较(a_extras的含义是Automatically selected extras),如果a_extras[i]跟通过筛选的token一样,则a_extras[i].hint_cnt加一,这代表这个token在语料里出现的次数。然后跳转到sort_a_extras.
- sort_a_extras用了两个快排,对a_extras进行排序。第一个是根据使用次数进行降序排序。第二个是对根据size进行排序。
- 排序完毕之后进行的操作是:
- 首先判断a_extras_cnt是否小于MAX_AUTO_EXTRAS,如果小于,则表示a_extras数组没有被填满,所以此时直接将候选token加入到a_extra数组里。
- 如果a_extras_cnt大于MAX_AUTO_EXTRAS,则从a_extras数组的后半部分里面随机选择一个元素,用候选token替换,也就是更改a_extras[i].data,a_extras[i].len。然后将a_extras[i].hit_cnt设置为0.
4:阶段衔接
- new_hit_cnt = queued_paths + unique_crashes;更新new_hit_cnt为queued_paths + unique_crashes
- stage_finds[STAGE_FLIP1] += new_hit_cnt - orig_hit_cnt; stage_finds[STAGE_FLIP1]的值加上在整个FLIP_BIT中新发现的路径和Crash总和
- stage_cycles[STAGE_FLIP1] += stage_max;stage_cycles[STAGE_FLIP1]的值加上在整个FLIP_BIT中执行的target次数stage_max
5:stage_name = “bitflip 2/1” 和 bitflip 4/1差不多原理,区别在于 FLIP_BIT的调用次数
for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {
stage_cur_byte = stage_cur >> 3;
FLIP_BIT(out_buf, stage_cur);
FLIP_BIT(out_buf, stage_cur + 1);
if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
FLIP_BIT(out_buf, stage_cur);
FLIP_BIT(out_buf, stage_cur + 1);
}
6:衔接一样
7:stage_name = “bitflip 8/8”;
- 位翻转方法: out_buf[stage_cur] ^= 0xFF;
- token获取:通过eff_map数组,里面数值是0/1,用来标记
- 总体解释:
- 为什么是 8/8 的时候出现?因为 8bit(比特)的时候是 1byte(字节),如果一个字节的翻转都无法带来路径变化,此byte极有可能是不会导致crash的数据,所以之后应该用一种思路避开无效byte。标记好的数组可以为之后的变异服务,相当于提前“踩雷(踩掉无效byte的雷)”,相当于进行了启发式的判断。无效为0,有效为1。要知道判断的时间开销,对不停循环的fuzzing过程来说是致命的,所以 eff_map 利用在这一次8/8的判断中,通过不大的空间开销,换取了可观的时间开销。(暂时是这样分析的,具体是否真的节约很多,不得而知)
- 实现方法
- 与之前找token的方式相似,如果byte翻转生成了新路径,就让这个byte在effector map中位置为1,否则为0。目的也是让后续变异参考,确认哪些位置是关键的参数,绕过无用的数据。
- 初始只有第一个、最后一个位置为1
- 每次发现新路径设置1
- 发现有效位超过90%直接全为1
- 注意,如果采用dumb mode或从fuzzer后续不会用到effector map的结果
8:衔接一样
9:stage_name = “bitflip 16/8”;stage_name = “bitflip 32/8”;,通过stage_max 和循环进行异或翻转。
ARITHMETIC INC/DEC
1:加减变异操作:可能测试易于整数溢出的数据
- arith 8/8,每次8bit进行加减运算,8bit步长从头开始,即对每个byte进行整数加减变异;
- arith 16/8,每次16bit进行加减运算,8bit步长从头开始,即对每个word进行整数加减变异;
- arith 32/8,每次32bit进行加减运算,8bit步长从头开始,即对每个dword进行整数加减变异;
2:stage_name = “arith 8/8”;
- 第一步遍历M2区域,也就是out_buf中的所有字节,将其赋值给orig,然后判断这个字节在eff_map中是否不为0,如果为0,则stage_max -= 2 *ARITH_MAX.进入下一轮循环。如果不为0,则继续执行后续代码。
- 表示出当前正在操作的字节:stage_cur_byte = i;
- 然后进行对orig字节的加减运算变异。
- 令j从1到ARITH_MAX遍历
- 加法变异的代码为:r = orig ^ (orig + j);
- 减法变异的代码为:r = orig ^ (orig - j);
- 需要调用could_be_bitflip函数对r进行一次判断,判断r是否可以通过bitflip方式得到,如果可以,则说明这是个冗余变异,不会被采纳。
- 恢复原case: out_buf[i] = orig;
3:stage_name = “arith 16/8” ;stage_name = “arith 32/8”
- 大小端的处理
- SWAP16:交换高八位和低八位ABDC->CDAB
- SWAP32:交换高八位和低八位,交换次高高位和次低八位ABCDEFGH->GHEFCDAB
- 溢出判断
- (orig & 0xff) + j > 0xff;(orig & 0xffff) + j > 0xffff(小端加)
- (orig & 0xff) < j;(orig & 0xffff) < j(小端减)
- (orig >> 8) + j > 0xff; (SWAP32(orig) & 0xffff) + j > 0xffff(大端加)
- (orig >> 8) < j;(SWAP32(orig) & 0xffff) < j (大端减)
INTERESTING VALUES
- 替换变异,用一系列确定的“interesting values”对内容进行替换,替换语料在config.h中,基本都是些会造成溢出的值
#define INTERESTING_8 \
-128, /* Overflow signed 8-bit when decremented */ \
-1, /* */ \
0, /* */ \
1, /* */ \
16, /* One-off with common buffer size */ \
32, /* One-off with common buffer size */ \
64, /* One-off with common buffer size */ \
100, /* One-off with common buffer size */ \
127 /* Overflow signed 8-bit when incremented */
#define INTERESTING_16 \
-32768, /* Overflow signed 16-bit when decremented */ \
-129, /* Overflow signed 8-bit */ \
128, /* Overflow signed 8-bit */ \
255, /* Overflow unsig 8-bit when incremented */ \
256, /* Overflow unsig 8-bit */ \
512, /* One-off with common buffer size */ \
1000, /* One-off with common buffer size */ \
1024, /* One-off with common buffer size */ \
4096, /* One-off with common buffer size */ \
32767 /* Overflow signed 16-bit when incremented */
#define INTERESTING_32 \
-2147483648LL, /* Overflow signed 32-bit when decremented */ \
-100663046, /* Large negative number (endian-agnostic) */ \
-32769, /* Overflow signed 16-bit */ \
32768, /* Overflow signed 16-bit */ \
65535, /* Overflow unsig 16-bit when incremented */ \
65536, /* Overflow unsig 16 bit */ \
100663045, /* Large positive number (endian-agnostic) */ \
2147483647 /* Overflow signed 32-bit when incremented */
2: stage_name = “interest 8/8”;
- 用8bit变量orig承接out_buf
- 判断这个字节在eff_map中是否有效,如果无效,则stage_max -= sizeof(interesting_8),进入下一次循环。如果有效,继续执行内部循环。
- 设置当前操作字节为i:stage_cur_byte = i;
- 然后进入内层循环:
- 首先判断当前变异是否可以通过单比特翻转,加减运算变异得到。
- 如果不可以,则用interesting_8对目标进行替换。
- 然后惯例调用common_fuz_stuff运行变异后的testcase:
- 2字节替换和4字节替换的原理也是一样的。
不过,2字节替换会检查该变异能否通过单字节变异得到,以此筛选冗余变异;4字节替换也会检查能否通过2字节替换和单字节替换得到。此外,2字节替换和4字节替换都会注意到大小端序的问题。
DICTIONARY STUFF
1:字典替换变异:会把自动生成或者用户提供的token替换、插入到原文件中。
- user extras (over),从头开始,将用户提供的tokens依次替换到原文件中
- user extras (insert),从头开始,将用户提供的tokens依次插入到原文件中
- auto extras (over),从头开始,将自动检测的tokens依次替换到原文件中
- 其中 “用户提供的tokens” 是一开始通过 -x 选项指定的,如果没有则跳过对应的子阶段;“自动检测的tokens” 是第一个阶段 bitflip 生成的。
-
- 首先判断extras_cnt是否为空,如果为空则说明用户没有提供extra token,会直接跳过这个阶段。去auto extras(over)
1:stage_name = “user extras (over)”;
- 8bit为步长,标记起始位置开始,替换为token,每个字节都替换一遍
- 遍历用户字典
- 出现以下情况,直接下一条token
- 字典token数>200,随机生成一个小于字典token数,仍>=200
- 替换token后长度超过case原大小
- case中数据与token一致
- eff_map为0
- 替换,执行
- 所有token结束后恢复,跳回步骤1
3:stage_name = “user extras (insert)”; - 以8bit为步长,标记起始位置插入token
memcpy(ex_tmp + i, extras[j].data, extras[j].len);
/* Insert token */
memcpy(ex_tmp + i, extras[j].data, extras[j].len);/
/* Copy tail */
memcpy(ex_tmp + i + extras[j].len, out_buf + i, len - i);
4:auto extras (over)
- 以8bit为步长,标记起始位置开始,替换为在bitflip阶段生成的token
非确定性变异(任何模式下都要进行)
RANDOM HAVOC
1:随机组合变异:各种变异策略的随机组合
- 对于非dumb mode的主fuzzer来说,完成了上述deterministic fuzzing后,便进入了充满随机性的这一阶段;对于dumb mode或者从fuzzer来说,则是直接从这一阶段开始。
- 首先判断splice_cycle这个标识符是否有效,如果无效,则标记此阶段为havoc;如果有效,则标记此阶段为splice。
- 然后通过21个case,来进行各种变异策略的执行:
- 外层循环stage_max轮stage(最小为16),在每一轮stage中首先产生随机数use_stacking
- 内层循环use_stacking轮,即一次stage中变化的次数(这是个随机的值,范围在2~14之间)
- 进入switch语句,分支条件随机生成一个数UR(15 + 2 + (region_level_mutation ? 4 : 0))(范围在0到16或0到20之间):
- case0:随机选取某个bit进行翻转
- case1: 随机选中interesting_8[]中的某个byte随机替换out_buf中的某个byte
- case2: 随机选中interesting_16[]中的某个word随机替换out_buf中的某个word(大小端序随机选择)
- case3: 随机选中interesting_32[]中的某个dword随机替换out_buf中的某个dword(大小端序随机选择)
- case4: 随机选中out_buf中的某个byte,减去一个随机的值(范围1~35)
- case5: 随机选中out_buf中的某个byte,加上一个随机的值(范围1~35)
- case6: 随机选中out_buf中的某个word,减去一个随机的值(大小端随机选择)
- case7: 随机选中out_buf中的某个word,加上一个随机的值(大小端随机选择)
- case8: 随机选中out_buf中的某个dword,减去一个随机的值(大小端随机选择)
- case9: 随机选中out_buf中的某个dword,加上一个随机的值(大小端随机选择)
- case10: 随机选中out_buf中的某个byte与范围在1~255中的一个随机值进行异或,从而将其变为一个随机数
- case11: 随机选中out_buf中的某个byte进行删除
- case12: 同case11,稍微增加一丢丢删除byte的概率,从而使得case更加精炼
- case13: 随机选中out_buf中的某个位置,插入随机长度的内容:
- a.75%的概率,插入一段out_buf中随机选择的一段内容
- b.25%的概率,插入一段相同的随机数字(50%概率是随机生成的,50%概率是从out_buf中随机选取的一个byte)
- case14: 随机选中out_buf中的某个位置,覆盖随机长度的内容:
- a.75%的概率,覆盖一段out_buf中随机选择的一段内容
- b.25%的概率,覆盖一段相同的随机数字(50%概率是随机生成的,50%概率是从out_buf中随机选取的一个byte)
- case15: 随机选中out_buf中的某个位置,覆盖成extra token(token来自用户提供的extras和自动生成的a_extras)
- case16: 随机选中out_buf中的某个位置,插入extra token(token来自用户提供的extras和自动生成的a_extras)
- case17(开启区域级变异才可以选择):Replace the current region with a random region from a random seed
- case18(开启区域级变异才可以选择):Insert a random region from a random seed to the beginning of the current region
- case19(开启区域级变异才可以选择):Insert a random region from a random seed to the end of the current region
- case20(开启区域级变异才可以选择):Duplicate the current region
SPLICING
1:拼接”,两个文件拼接到一起得到一个新文件
- 随机选择一个文件与当前文件进行对比,如果差别不大就重新再选;如果差异明显,就随机选取位置两个文件都一切两半。最后将当前文件的头与随机文件的尾拼接起来得到新文件,本着“减少消耗”的原则拼接后的文件应该与上一个文件对比,如果未发生变化应该过滤掉。
- 然后依靠破坏代码来进行变异。
- 因为之前的变异结果不佳才会调用这个阶段,所以首先要对in_buf进行clean。
- 然后随机选择一个queue:
- 确保目标具有合适的长度:
- 将目标testcase读取到一个新的缓冲区
- 在new_buf的第一个字节和最后一个字节之间找到一个合适的位置。
- 然后通过locate_diffs对in_buf和new_buf进行比较,定位两个buffer不同的地方,找到之后就从此处开始拼接两个buffer
- 在不同部分的第一个字节和最后一个字节之间随即寻找位置:
- 开始进行拼接
- 然后 goto havoc_stage;
abandon_entry
- 如果之前的变异效果不是很好,才会调用splice过程,如果效果好,有所发现,那么就会来到abandon_entry。
- 首先设置ret_val = 0。
- 然后设置splicing_with = -1,表示不经历splice过程。
- 然后对于当前的queue的信息进行更新:
- 如果当前queue校准成功,且queue_cur->was_fuzzed = 0,则设置queue_cur->was_fuzzed = 1.然后更新was_fuzzed_map里的有关当前queue的信息,如果当前queue是favored的,则pending_favored减一。(同一个testcase,经历的fuzz次数越多,价值越少)
- 最后做一些缓冲区和数据结构的清理,然后返回ret_val.
cycle,循环往复,一波变异结束后的文件,会在队列结束后下一轮中继续变异下去
AFL状态栏右上角的 cycles done 意味着完成的循环数,每次循环是对整个队列的再一次变异,不过只有第一次 cycle 才会进行 deterministic fuzzing,之后的只有随机性变异了。
参考资料
https://blog.csdn.net/further_eye/article/details/120842471
https://www.codenong.com/cs105176422/
https://bbs.kanxue.com/thread-273639-1.htm#fuzz_one%E5%8F%98%E5%BC%82
https://jiuaidu.com/jianzhan/1016488/
https://blog.csdn.net/von_Neumann_/article/details/127768296