AFL原理简述

 

智能fuzz(smart fuzz)工具一般都包括几部分:



1 打桩

用afl-gcc编译后,每个代码块都会有afl_maybe_log这个函数。其中rcx中会有一个整数,每一块中该整数是不一样的。这个其实是afl-as按编译时间生成的一个随机数。并以此随机数来表示一个代码块(basic block)

 

路径检测

AFL使用了共享内存来记录路径。在主进程中(afl-fuzz)会申请一块匿名内存。

首先调用shemget()分配一块共享内存,大小MAP_SIZE为64K。这是二级缓存的大小,尽量使用内存读取速度快,增加fuzz的效率。

shm_id = shmget(IPC_PRIVATE, MAP_SIZE,IPC_CREAT | IPC_EXCL | 0600);

 

分配成功后,设置该共享内存的标志符到到环境变量中,后面子进程从环境变量中获得该匿名内存的标识符。

shm_str = alloc_printf("%d",shm_id);
if (!dumb_mode)

setenv(SHM_ENV_VAR,shm_str, 1);

 

理论上要记录A-B此路径,需要记录A地址和B地址来表示此路径。当路径经过许多代码块时,这个数量是非常大的。而AFL用了一个取巧的办法来平衡数据量和效率(牺牲了一点准确性)。

 

AFL源码附录的白皮书,AFL是根据跳转源地址和目标地址的信息来记录路径的。伪代码如下:(实际此段代码是在afl_maybe_log中,以汇编语言实现)

 cur_location = <COMPILE_TIME_RANDOM>;    //就是打桩时加入的随机数

 shared_mem[cur_location ^ prev_location]++;       //shared_mem就是上面提及的共享内存

  prev_location = cur_location>> 1;   //a-b和b-a路径在shared_mem里是不一样的,减少冲突

所有路径,不论多复杂,都是用同一个大小的shared_mem来表示。可是这个仍然很大(64K),为了减少冲突,AFL把这个最终生成的shared_mem再进行一次哈希运算,生成一个32字节数据。以这个数据来表示一条路径。这样表示一条路径从64K到32字节,大大节省了空间。

 

这样的好处有:

1、a-b和b-a路径在shared_mem里是不一样的。

2、省空间,只需要一个64K匿名内存就保存了所有路径

3、效率高,因为全部是在内存中操作,不需要访问外存

坏处:

由于shared_mem某些位可能会翻转,而且哈希算法也可能存在碰撞。这种做法是存在碰撞机率的,但在一般软件中,这个碰撞机率可以接受。

 

 

AFL缺点:

1、 无状态。极低机率进入下一状态机中。

如TCP,三次握手的响应是不能有效测试的。应该使用有状态的fuzz工具,如peach。

2、 魔术字情况难以进入

考虑如下代码

if( !strcmp(input,”magic”))

       vulnerability();

当input的值为magic时才能触发,如果是完全随机的变异,生成magic输入的概率为

1/(255*255*255*255*255)。

如果更长的魔术字就更低概率了。

不过这个问题有2个解决办法,

1、先从文件中取出所有字符串,加入到afl的字典中,后面变异会从字典中取值(有一种变异策略是从字典读输入)

2、在上一节路径检测中提到a-b和b-a记录的路径是不一样的,因此可在strcmp中插桩,把strcmp当成fuzz程序的一部分来进行fuzz。还是这个例子,此时的概率就是

255+255+255+255+255。


参考:

AFL内部实现细节小记     http://rk700.github.io/2017/12/28/afl-internals/



阅读更多
文章标签: FUZZ AFL
个人分类: fuzz
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭