afl-as注释好了,明白了是如何进行插桩的,以及插在哪里
本文主要参考Sakura大佬文章:https://eternalsakura13.com/2020/08/23/afl/
编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ
模糊测试系列往期回顾:
AFL源码分析之afl-gcc.c详细注释
#define AFL_MAIN #include "config.h" #include "types.h" #include "debug.h" #include "alloc-inl.h" #include "afl-as.h" #include#include #include #include #include #include #include #include #include static u8** as_params; static u8* input_file; static u8* modified_file; static u8 be_quiet, clang_mode, pass_thru, just_version, sanitizer; static u32 inst_ratio = 100, as_par_cnt = 1; #ifdef WORD_SIZE_64 static u8 use_64bit = 1; //64位标志 #else static u8 use_64bit = 0; //32位标志 #ifdef __APPLE__ //如果是苹果平台 # error "Sorry, 32-bit Apple platforms are not supported." //提示苹果平台不支持32位 #endif #endif static void edit_params(int argc, char** argv) { u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); //获取环境变量“TMPDIR”和“AFL_AS” u32 i; #ifdef __APPLE__ //如果是APPLE平台 u8 use_clang_as = 0; if (clang_mode && !afl_as) { //如果使用clang模式并且没有获取到“AFL_AS”环境变量则进入分支 use_clang_as = 1; //设置use_clang_as变量为1 afl_as = getenv("AFL_CC"); if (!afl_as) afl_as = getenv("AFL_CXX"); if (!afl_as) afl_as = "clang"; //将afl_as赋值为“AFL_CC”、“AFL_CXX”环境变量或“clang”中的一种 } #endif if (!tmp_dir) tmp_dir = getenv("TEMP"); if (!tmp_dir) tmp_dir = getenv("TMP"); if (!tmp_dir) tmp_dir = "/tmp"; //为tmp_dir赋值为"TMPDIR"、“TEMP”、“TMP”环境变量或“/tmp”中的一种 as_params = ck_alloc((argc + 32) * sizeof(u8*)); //为as_params开辟空间 as_params[0] = afl_as ? afl_as : (u8*)"as"; //afl_as是否已经获取到“AFL_AS”环境变量,如果获取到了就将环境变量值赋值给as_param[0],如果没有获取到,“as”字符串赋值给afl_as as_params[argc] = 0; //设置最后一个参数为0 for (i = 1; i < argc - 1; i++) { //从第一个参数开始遍历,到最后一个参数 if (!strcmp(argv[i], "--64")) use_64bit = 1; //如果遍历到“--64”参数,则设置use_64bit变量为1 else if (!strcmp(argv[i], "--32")) use_64bit = 0; //如果遍历到“--32”参数,则设置use_64bit变量为0 #ifdef __APPLE__ //如果是APPLE平台 if (!strcmp(argv[i], "-arch") && i + 1 < argc) { //如果遍历到“-arch”参数 if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1; //如果是"-arch x86_64",则设置use_64bit为1 else if (!strcmp(argv[i + 1], "i386")) //如果是“-arch i386”,则报错 FATAL("Sorry, 32-bit Apple platforms are not supported."); } if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q"))) continue; //如果是clang模式,并且遍历的是“-q”或者“-Q”参数,直接跳出循环 #endif as_params[as_par_cnt++] = argv[i]; } #ifdef __APPLE__ //APPLE平台 if (use_clang_as) { as_params[as_par_cnt++] = "-c"; as_params[as_par_cnt++] = "-x"; as_params[as_par_cnt++] = "assembler"; //如果使用的时clang模式,追加参数“-c -x assembler” } #endif input_file = argv[argc - 1]; //将最后一个参数的值赋给input_file变量 if (input_file[0] == '-') { //如果input_file的首字母为“-” if (!strcmp(input_file + 1, "-version")) { //如果是“-version” just_version = 1; //设置just_version值为1 modified_file = input_file; //modified设置为“-version” goto wrap_things_up; //跳转到参数组合结尾 } if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)"); //如果“-”后不是version,则抛出异常 else input_file = NULL; } else { if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) && strncmp(input_file, "/var/tmp/", 9) && strncmp(input_file, "/tmp/", 5)) pass_thru = 1; //如果首字母不是“-”,则比对input_file的前strlen(tmp_dir)、9、5个字节是否与tmp_dir、"/var/tmp/"、"/tmp/"是否相同,如果都不相同则设置pass_thru为1 } modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(), (u32)time(NULL)); //设置modified_file为类似tmp_dir/.afl-pid-time.s这样的字符串 wrap_things_up: as_params[as_par_cnt++] = modified_file; //接收参数为modified最后一个参数 as_params[as_par_cnt] = NULL; //参数接收结束 } static void add_instrumentation(void) { //处理输入文件,生成modified_file,将桩插入所有释放的位置 static u8 line[MAX_LINE]; FILE* inf; FILE* outf; s32 outfd; u32 ins_lines = 0; u8 instr_ok = 0, skip_csect = 0, skip_next_label = 0, skip_intel = 0, skip_app = 0, instrument_next = 0; #ifdef __APPLE__ u8* colon_pos; #endif if (input_file) { //如果存在输入文件名称 inf = fopen(input_file, "r"); //尝试获取input_file句柄,将fd赋值给inf if (!inf) PFATAL("Unable to read '%s'", input_file); //如果获取不到,抛异常 } else inf = stdin; //如果不存在文件名则赋值标准输入 outfd = open(modified_file, O_WRonLY | O_EXCL | O_CREAT, 0600); //以写的方式打开modified_file,如果文件已存在就直接打开,如果没有就创建一个 if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file); //如果文件没有写权限,则抛出异常 outf = fdopen(outfd, "w"); //尝试打开 if (!outf) PFATAL("fdopen() failed"); //打不开抛异常 while (fgets(line, MAX_LINE, inf)) { //循环读取inf指向的文件的每一行到line数组,每行最多MAX_LINE(8192)个字节,含末尾“ ” if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok && instrument_next && line[0] == 't' && isalpha(line[1])) { //446行判断是否为defered mode模式 fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE)); //插桩 instrument_next = 0; //instrument_next重新设置为1 ins_lines++; //插桩计数器+1 } fputs(line, outf); if (pass_thru) continue; if (line[0] == 't' && line[1] == '.') { //判断读入的行是否以“t”开头,并且line[1]是否为"." if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) && isdigit(line[10]) && line[11] == 'n') skip_next_label = 1; //检查是否为p2align指令,如果是则设置skip_next_label为1 //instr_ok变量是一个flag,如果为1表示位于.text段,如果为0表示不再.text段 if (!strncmp(line + 2, "textn", 5) || !strncmp(line + 2, "sectiont.text", 13) || !strncmp(line + 2, "sectiont__TEXT,__text", 21) || !strncmp(line + 2, "section __TEXT,__text", 21)) { instr_ok = 1; continue; //匹配"textn"、"sectiont.text"、"sectiont__TEXT,__text"、"section __TEXT,__text",如果匹配成功则设置instr_ok为1。跳出本次循环 } if (!strncmp(line + 2, "sectiont", 8) || !strncmp(line + 2, "section ", 8) || !strncmp(line + 2, "bssn", 4) || !strncmp(line + 2, "datan", 5)) { instr_ok = 0; continue; //匹配"sectiont"、"section "、"bssn"、"datan",如果匹配成功说明不是在.text段,设置instr_ok变量为0 } } if (strstr(line, ".code")) { //判断架构 if (strstr(line, ".code32")) skip_csect = use_64bit; if (strstr(line, ".code64")) skip_csect = !use_64bit; } if (strstr(line, ".intel_syntax")) skip_intel = 1; //判断是否为Intel汇编语法 if (strstr(line, ".att_syntax")) skip_intel = 0; //判断是否为att汇编语法 if (line[0] == '#' || line[1] == '#') { //ad-hoc __asm__块是否跳过 if (strstr(line, "#APP")) skip_app = 1; if (strstr(line, "#NO_APP")) skip_app = 0; } if (skip_intel || skip_app || skip_csect || !instr_ok || line[0] == '#' || line[0] == ' ') continue; if (line[0] == 't') { if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) { //对于形如tj[^m].格式的指令,即条件跳转指令,且R()函数创建的随机数小于插桩密度inst_ratio fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE)); //判断是否为64位程序,使用fprintf函数将桩插在outf只想的文件的tj[^m].跳转指令位置,插入长度为R函数创建的小于MAP_SIZE的随机数 ins_lines++; //插桩计数器+1,跳出循环进行下一次遍历 } continue; } #ifdef __APPLE__ if ((colon_pos = strstr(line, ":"))) { //检查line中是否存在“:” if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { //检查是否以“.”开始 #else if (strstr(line, ":")) { //检查line中是否存在“:” if (line[0] == '.') { //检查是否以“.”开始 #endif #ifdef __APPLE__ if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) && R(100) < inst_ratio) { #else if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) && R(100) < inst_ratio) { //检查line[2]是否为数字,或者在clang模式下从line[1]开始的三个字节是否为LBB,并且随机数小于插桩密度 #endif if (!skip_next_label) instrument_next = 1; else skip_next_label = 0; //设置instrument_next为1 } } else { instrument_next = 1; //否则代表这是一个function label,插桩^func,设置instrument_next为1(defered mode) } } } if (ins_lines) //如果插桩计数器不为0 fputs(use_64bit ? main_payload_64 : main_payload_32, outf); //向outf中写入main_payload_64或main_payload_32 if (input_file) fclose(inf); //关闭文件 fclose(outf); //关闭文件 if (!be_quiet) { //如果使用的不是静默模式 if (!ins_lines) WARNF("No instrumentation targets found%s.", //如果插桩计数器为空,抛异常 pass_thru ? " (pass-thru mode)" : ""); else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).", //插桩成功输出 ins_lines, use_64bit ? "64" : "32", getenv("AFL_HARDEN") ? "hardened" : (sanitizer ? "ASAN/MSAN" : "non-hardened"), inst_ratio); } } int main(int argc, char** argv) { s32 pid; u32 rand_seed; int status; u8* inst_ratio_str = getenv("AFL_INST_RATIO"); //该环境变量主要控制检测每个分支的概率,取值为0到100%,设置为0时值检测函数入口的跳转,而不会检测函数分支的跳转 struct timeval tv; struct timezone tz; clang_mode = !!getenv(CLANG_ENV_VAR); if (isatty(2) && !getenv("AFL_QUIET")) { SAYF(cCYA "afl-as " cBRI VERSION cRST " by n"); } else be_quiet = 1; if (argc < 2) { SAYF("n" "This is a helper hollk application for afl-fuzz. It is a wrapper around GNU 'as',n" "executed by the toolchain whenever using afl-gcc or afl-clang. You probablyn" "don't want to run this program directly.nn" "Rarely, when dealing with extremely hollk complex projects, it may be advisable ton" "set AFL_INST_RATIO to a value less than 100 in order to reduce the odds ofn" "instrumenting every discovered branch.nn"); exit(1); } gettimeofday(&tv, &tz); //获取当前精确时间 rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid(); //通过当前时间与进程pid进行亦或处理 srandom(rand_seed); //获得随机化种子 edit_params(argc, argv); //检查并修改参数以传递给“as”。文件名是以GCC传递的最后一个参数决定的。此函数主要设置变量as_params的值,以及use_64bit/modified_file的值 if (inst_ratio_str) { //如果获取到"AFL_INST_RATIO"环境变量则进入分支 if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100) //如果没有将覆盖率写入inst_ratio变量或者inst_ratio中的值超过100的话,则进入分支抛出异常 FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)"); } if (getenv(AS_LOOP_ENV_VAR)) //如果获取到"__AFL_AS_LOOPCHECK"环境变量值,则进入分支 FATAL("Endless loop when calling 'as' (remove '.' from your PATH)"); //抛出异常 setenv(AS_LOOP_ENV_VAR, "1", 1); //设置"__AFL_AS_LOOPCHECK"环境变量为1 if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) { //如果获取到"AFL_USE_ASAN"或"AFL_USE_MSAN"环境变量,如果其中有一个值为1则进入分支 sanitizer = 1; //sanitizer设置为1 inst_ratio /= 3; //inst_ratio除以3 } if (!just_version) add_instrumentation(); //如果不是只查询version,那么就会进入add_instrumentastion()函数,该函数主要处理输入文件,生成modified_file,将桩插入释放的位置 if (!(pid = fork())) { //调用fork函数创建一个子进程。在执行execvp函数执行是 execvp(as_params[0], (char**)as_params); //执行命令和参数 FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]); //不成功抛异常 } if (pid < 0) PFATAL("fork() failed"); //如果创建子进程失败,抛出异常 if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed"); //等待子进程结束 if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file); //读取环境变量"AFL_KEEP_ASSEMBLY"失败,则unlink掉modified_file //设置该环境变量主要是为了防止afl-as删掉插桩后的汇编文件,设置为1会保留插桩后的汇编文件 exit(WEXITSTATUS(status)); }



