AFL使用边覆盖作为覆盖信息度量,相比于块覆盖能够包含更多信息,相比于路径覆盖更易于操作,是一种择中的考虑。AFL的反馈机制是将边散列到64KB位图中的一个位置。下面介绍几个概念
基本块:程序顺序执行的语句序列,只有一个入口,一个出口
只有一个入口,表示程序中不会有其它任何地方能通过jump跳转类指令进入到此基本块中。
只有一个出口,表示程序只有最后一条指令能导致进入到其它基本块去执行。
所以,基本块的一个典型特点是:只要基本块中第一条指令被执行了,那么基本块内所有执行都会按照顺序仅执行一次。
边:一个基本块跳转到另一个基本块
位图
AFL中有四个关键位图
trace_bits[MAP_SIZE] // 当前用例执行情况
virgin_bits[MAP_SIZE] // 所有用例执行情况
virgin_tmout[MAP_SIZE] // 超时用例执行情况
virgin_crash[MAP_SIZE] // 崩溃用例执行情况
对于边的覆盖情况计数,通过下面操作实现
cur_location = R(MAP_SIZE); // 给基本快随机赋值
sh_mem[cur_location ^ prev_location]++; // 边访问计数
prev_location = cur_location >> 1; // 避免出现A-A和B-B,A-B和B-A无法区分的问题
其中cur_location ^ prev_location作为实际的边id,访问到这个边的时候,相应下标处访问次数加1
simplify_trace函数
simplify_trace函数所做的操作是将边实际的执行次数划分为执行过和未执行过两类,如果未执行过,对应的位置为1,如果执行过,对应的位置为128。
static const u8 simplify_lookup[256] = {
[0] = 1,
[1 ... 255] = 128
};
// if mem8[i] == 0 mem8[i] = 1; 0000 0001 not hit new edge
// if mem8[i] == 1~255 mem8[i] = 128; 1000 0000 hit new edge
static void simplify_trace(u64* mem) {
u32 i = MAP_SIZE >> 3;
while (i--) {
if (unlikely(*mem)) {
u8* mem8 = (u8*)mem;
mem8[0] = simplify_lookup[mem8[0]];
mem8[1] = simplify_lookup[mem8[1]];
mem8[2] = simplify_lookup[mem8[2]];
mem8[3] = simplify_lookup[mem8[3]];
mem8[4] = simplify_lookup[mem8[4]];
mem8[5] = simplify_lookup[mem8[5]];
mem8[6] = simplify_lookup[mem8[6]];
mem8[7] = simplify_lookup[mem8[7]];
} else *mem = 0x0101010101010101ULL;
mem++;
}
}
classify_counts函数
classify_counts函数的功能是把实际的执行次数进行重新计数,分到8个计数桶中,即0, 1, 2, 4, 8, 16, 32, 64, 128,0~255之间的实际执行次数最终都会被分到这8个计数桶中,从而对位图重新赋值
static const u8 count_class_lookup8[256] = {
[0] = 0,
[1] = 1,
[2] = 2,
[3] = 4,
[4 ... 7] = 8,
[8 ... 15] = 16,
[16 ... 31] = 32,
[32 ... 127] = 64,
[128 ... 255] = 128
};
static u16 count_class_lookup16[65536];
EXP_ST void init_count_class16(void) {
u32 b1, b2;
for (b1 = 0; b1 < 256; b1++)
for (b2 = 0; b2 < 256; b2++)
count_class_lookup16[(b1 << 8) + b2] =
(count_class_lookup8[b1] << 8) |
count_class_lookup8[b2];
}
static inline void classify_counts(u64* mem) {
u32 i = MAP_SIZE >> 3;
while (i--) {
if (unlikely(*mem)) { // if *mem != 0
// 对每8B做如下操作,实现计数
u16* mem16 = (u16*)mem;
mem16[0] = count_class_lookup16[mem16[0]];
mem16[1] = count_class_lookup16[mem16[1]];
mem16[2] = count_class_lookup16[mem16[2]];
mem16[3] = count_class_lookup16[mem16[3]];
}
mem++;
}
}
has_new_bits函数
这个函数用来度量是否出现了新的覆盖,使用trace_bits和另外三个位图比较,判断是否有新的覆盖,返回0表示没有,返回1表示边被访问过了,但产生了新的执行次数,返回2表示执行到了新的边,返回1和2都被视为有新的覆盖。save_if_interesting函数就是调用has_new_bits函数来判断当前用例是否是interesting的
virgin位图会初始化为全1
*current为true且*current & *virgin为true说明当前用例执行到了一些边且在该执行情况在virgin位图中还没有出现过,
*virgin &= ~*current;语句将表示当前执行情况的位在virgin位图中标出,下次在出现相同的执行情况时if语句将判断为false,即没有出现新的情况
举个例子,某个边初始为1111 1111,一个用例执行到该边4次,在trace_bits位图中表示为0000 1000。
~*current为1111 0111,与*virgin进行与操作后为1111 0111,此时*virgin被赋值为1111 0111。
下次再有一个用例执行到该边,执行次数为4,5,6,7时,经过classify_counts函数计数后在trace_bits位图中为0000 1000,与*virgin即1111 0111做与操作为false
因此一个边的某个执行次数被触发过,下一次再有用例触发同一个计数桶中的执行次数时,将不被视为新的情况
static inline u8 has_new_bits(u8* virgin_map) {
u64* current = (u64*)trace_bits;
u64* virgin = (u64*)virgin_map;
u32 i = (MAP_SIZE >> 3);
u8 ret = 0;
while (i--) {
if (unlikely(*current) && unlikely(*current & *virgin)) {
if (likely(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) ||
(cur[4] && vir[4] == 0xff) || (cur[5] && vir[5] == 0xff) ||
(cur[6] && vir[6] == 0xff) || (cur[7] && vir[7] == 0xff)) ret = 2;
else ret = 1;
}
*virgin &= ~*current;
}
current++;
virgin++;
}
if (ret && virgin_map == virgin_bits) bitmap_changed = 1;
return ret;
}