介绍AFLGO的文章提到AFLGO在AFL的LLVM基础上做出了修改。
首先说一下AFL的LLVM模式。
AFL LLVM
不同于afl-gcc对gcc做了一个rapper,在编译的时候会识别jmp、call之类的跳转并插入跳板代码实现插桩,AFL的LLVM则利用了编写pass文件的方法实现的插桩。
LLVM的pass介绍
pass文件可以在LLVM的clang进行编译的时候同时对函数、基本块等元素进行操作,详情可见(pass编写)
afl-clang-fast
在CC改成afl-clang-fast,就可以使用改过的afl-clang-fast对源码进行编译。把afl-llvm-rt.o中的函数载入,
然后在编译选项是加入afl-llvm-rt.o。
afl-llvm-pass.so
在pass文件中先是重载了ModulePass类里面的runOnModule方法。
class AFLCoverage : public ModulePass {
public:
static char ID;
AFLCoverage() : ModulePass(ID) { }
bool runOnModule(Module &M) override;
// StringRef getPassName() const override {
// return "American Fuzzy Lop Instrumentation";
// }
};
然后设置全局变量bitmap地址和上一个BB地址。
GlobalVariable *AFLMapPtr =
new GlobalVariable(M, PointerType::get(Int8Ty, 0), false,
GlobalValue::ExternalLinkage, 0, "__afl_area_ptr");
GlobalVariable *AFLPrevLoc = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc",
0, GlobalVariable::GeneralDynamicTLSModel, 0, false);
然后循环,对每一个函数,每一个BB进行插桩操作。
for (auto &F : M)
for (auto &BB : F) {
...
}
}
在运行程序的时候同时会执行pass文件里面的程序,即运行的每一个BB都会update全局的bitmap。
afl-llvm-rt.o
这个相当于afl-gcc里面的mainpload。里面有init,shm_setup和persistent_loop的函数代码,插入在目标程序后,跳板就可以调用这些函数。
AFLGO编译
bitmap在原先的65526大小的基础上加了16个字节,前四个字节放置距离的倒数和,后四个字节放置触发的基本块的数量。
afl-llvm-pass.so统计
在pass文件中,在编译时先检查是否有Targets文件或者绘制好的distance文件。如果有Targets文件,就载入Targets文件并把变量is_aflgo_preprocessing置为1,即处于编译状态。
if (!TargetsFile.empty()) {
if (OutDirectory.empty()) {
FATAL("Provide output directory '-outdir <directory>'");
return false;
}
std::ifstream targetsfile(TargetsFile);
std::string line;
while (std::getline(targetsfile, line))
targets.push_back(line);
targetsfile.close();
is_aflgo_preprocessing = true;
}
创建了四个文件。
std::ofstream bbnames(OutDirectory + "/BBnames.txt", std::ofstream::out | std::ofstream::app);
std::ofstream bbcalls(OutDirectory + "/BBcalls.txt", std::ofstream::out | std::ofstream::app);
std::ofstream fnames(OutDirectory + "/Fnames.txt", std::ofstream::out | std::ofstream::app);
std::ofstream ftargets(OutDirectory + "/Ftargets.txt", std::ofstream::out | std::ofstream::app);
然后对每个函数、每个BB、每个指令进行循环。
识别指令是否时tagets文件中的XX源文件中的第几行,如果是,所在的BB、F设置为Tb,Tf,把function写入fnames和ftargets。
识别指令是否为跳转指令,是的话把当前BB和跳转函数的tuple写入BBcalls文件。然后把文件写入dotfile文件夹。
AFLGO运行
生成distance文件
在插桩后,运行script/getDistanse.sh
动态运行
已有distance文件就把distance载入到全局变量bb_to_dis表中并把is_aflgo置为1。
else if (!DistanceFile.empty()) {
std::ifstream cf(DistanceFile);
if (cf.is_open()) {
std::string line;
while (getline(cf, line)) {
std::size_t pos = line.find(",");
std::string bb_name = line.substr(0, pos);
int bb_dis = (int) (100.0 * atof(line.substr(pos + 1, line.length()).c_str()));
bb_to_dis.emplace(bb_name, bb_dis);
basic_blocks.push_back(bb_name);
}
cf.close();
is_aflgo = true;
}
然后插桩后,运行的每个BB都可以得到distance文件中计算好的距离。
运行过程中维持着这些数据:
static double cur_distance = -1.0; /* Distance of executed input */
static double max_distance = -1.0; /* Maximal distance for any input */
static double min_distance = -1.0; /* Minimal distance for any input */
static u32 t_x = 10; /* Time to exploitation (Default: 10 min) */
每次把新种子放入队列的时候,计算其距离。
/* Calculate distance of current input to targets */
u32* total_distance = (u32*)(trace_bits + MAP_SIZE);
u32* total_count = (u32*)(trace_bits + MAP_SIZE + 4);
if (*total_count > 0) {
cur_distance = (double) (*total_distance) / (double) (*total_count);
else
cur_distance = -1.0;
然后设置退火时间t_x,调度算法计算种子的havoc能量。在这个调度算法下实现了基于距离对种子的调度。
u64 cur_ms = get_cur_time();
u64 t = (cur_ms - start_time) / 1000;
double progress_to_tx = ((double) t) / ((double) t_x * 60.0);
double T;
switch (cooling_schedule) {
case SAN_EXP:
T = 1.0 / pow(20.0, progress_to_tx);
break;
case SAN_LOG:
T = 1.0 / (1.0 + 2.0 * log(1.0 + progress_to_tx * 13358.7268297));
break;
case SAN_LIN:
T = 1.0 / (1.0 + 19.0 * progress_to_tx);
break;
case SAN_QUAD:
T = 1.0 / (1.0 + 19.0 * pow(progress_to_tx, 2));
break;
default:
PFATAL ("Unkown Power Schedule for Directed Fuzzing");
}
double power_factor = 1.0;
if (q->distance > 0) {
double normalized_d = q->distance;
if (max_distance != min_distance)
normalized_d = (q->distance - min_distance) / (max_distance - min_distance);
if (normalized_d >= 0) {
double p = (1.0 - normalized_d) * (1.0 - T) + 0.5 * T;
power_factor = pow(2.0, 2.0 * (double) log2(MAX_FACTOR) * (p - 0.5));
}// else WARNF ("Normalized distance negative: %f", normalized_d);
}
perf_score *= power_factor;