从代码的角度学习AFLSMART的特性
本文为看雪论坛精华文章
看雪论坛作者ID:gaveu屯烫烫
1
简介
因此,针对当前工作对文件结构缺乏感知的问题,Van-Thuan Pham等人在AFL的基础上推出了AFLSMART,可以根据文件内部的层级结构信息产生新的种子文件,提升fuzzer的有效性与可用性。
在AFL的基础上,AFLSMART参照了 Peach 的 File Cracker 组件,将文件按块划分,抽象为一个可以用 xml 文件描述的树形的结构,并引入了如下特性:
虚拟结构表示(Virtual Structure)
文件块层次的突变(Smart Mutation Operators)
基于有效性的种子能量调度(Valid-based Power Schedule)
2
虚拟结构表示(Virtual Structure)
struct chunk { unsigned long id; /* The id of the chunk, which either equals its pointer value or, when loaded from chunks file, equals to the hashcode of its chunk identifer string casted to unsigned long. */ int type; /* The hashcode of the chunk type. */ int start_byte; /* The start byte, negative if unknown. */ int end_byte; /* The last byte, negative if unknown. */ char modifiable; /* The modifiable flag. */ struct chunk *next; /* The next sibling child. */ struct chunk *children; /* The children chunks linked list. */};case 'g': //han:set up input_model_file if (input_model_file) FATAL("Multiple -g options not supported");
input_model_file = optarg;
/* Check if exists */ FILE * f = fopen(input_model_file, "r"); if (f) fclose(f); else FATAL("File %s does not exist!", input_model_file);
break;/******************************************* * CALIBRATION (only if failed earlier on) * *******************************************/
if (queue_cur->cal_failed) {
u8 res = FAULT_TMOUT;
if (queue_cur->cal_failed < CAL_CHANCES) {
res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0);
if (res == FAULT_ERROR) FATAL("Unable to execute target application");
}
if (stop_soon || res != crash_mode) { cur_skipped_paths++; goto abandon_entry; }
}
/* Deferred cracking */if (smart_mode && !queue_cur->chunk && (initial_queue_num > 0 || UR(100) < (get_cur_time() - last_path_time) / 50)) { update_input_structure(queue_cur->fname, queue_cur); --initial_queue_num;}/* Add input structure information to the queue entry */static void update_input_structure(u8* fname, struct queue_entry* q) { pid_t pid = 0; int pipefd[2]; FILE* output; char line[256]; int status; u8* ifname; u8* ofname;
if (model_type == MODEL_PEACH) {
if (pipe(pipefd) < 0) { PFATAL("AFLSmart cannot create a pipe to communicate with Peach"); exit(1); }
pid = fork(); if (pid == 0) { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); ifname = alloc_printf("-inputFilePath=%s", fname); ofname = alloc_printf("-outputFilePath=%s/chunks/%s.repaired", out_dir, basename(fname));
execlp("peach", "peach", "-1", ifname, ofname, input_model_file, (char*) NULL); exit(1); /* Stop the child process upon failure. */ } else { close(pipefd[1]); output = fdopen(pipefd[0], "r");
while (fgets(line, sizeof(line), output)) { /* Extract validity percentage and update the current queue entry. */ q->validity = 0; if (!strncmp(line, "ok", 2)) { q->validity = 100; break; } else if (!strncmp(line, "error", 5)) { char *s = line + 5; while (isspace(*s)) { s++; } char *start = s; while (isdigit(*s)) { s++; } *s = '\0'; if (s != start) { q->validity = (u8) atoi(start); } break; } }
waitpid(pid, &status, 0);
u8* chunks_fname = alloc_printf("%s/chunks/%s.repaired.chunks", out_dir, basename(fname)); struct chunk *chunk;
get_chunks(chunks_fname, &chunk); q->chunk = chunk; q->cached_chunk = copy_chunks(chunk);
fclose(output); ck_free(chunks_fname); }
} else { /// NOT SUPPORTED PFATAL("AFLSmart currently only supports Peach models! Please use -w peach option"); }
parsed_inputs++; validity_avg += (s8)(q->validity - validity_avg) / parsed_inputs; q->parsed = 1;}/* Add input structure information to the queue entry */static void update_input_structure(u8* fname, struct queue_entry* q) { pid_t pid = 0; int pipefd[2]; FILE* output; char line[256]; int status; u8* ifname; u8* ofname;
if (model_type == MODEL_PEACH) {
if (pipe(pipefd) < 0) { PFATAL("AFLSmart cannot create a pipe to communicate with Peach"); exit(1); }
pid = fork(); if (pid == 0) { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); ifname = alloc_printf("-inputFilePath=%s", fname); ofname = alloc_printf("-outputFilePath=%s/chunks/%s.repaired", out_dir, basename(fname));
execlp("peach", "peach", "-1", ifname, ofname, input_model_file, (char*) NULL); exit(1); /* Stop the child process upon failure. */ } else { close(pipefd[1]); output = fdopen(pipefd[0], "r");
while (fgets(line, sizeof(line), output)) { /* Extract validity percentage and update the current queue entry. */ q->validity = 0; if (!strncmp(line, "ok", 2)) { q->validity = 100; break; } else if (!strncmp(line, "error", 5)) { char *s = line + 5; while (isspace(*s)) { s++; } char *start = s; while (isdigit(*s)) { s++; } *s = '\0'; if (s != start) { q->validity = (u8) atoi(start); } break; } }
waitpid(pid, &status, 0);
u8* chunks_fname = alloc_printf("%s/chunks/%s.repaired.chunks", out_dir, basename(fname)); struct chunk *chunk;
get_chunks(chunks_fname, &chunk); q->chunk = chunk; q->cached_chunk = copy_chunks(chunk);
fclose(output); ck_free(chunks_fname); }
} else { /// NOT SUPPORTED PFATAL("AFLSmart currently only supports Peach models! Please use -w peach option"); }
parsed_inputs++; validity_avg += (s8)(q->validity - validity_avg) / parsed_inputs; q->parsed = 1;}void get_chunks(char *filespec, struct chunk **data_chunks) { FILE *chunk_file; char *line; size_t n;
*data_chunks = NULL;
if ((chunk_file = fopen(filespec, "r")) == NULL) return;
do { line = NULL; n = 0; if (getline(&line, &n, chunk_file) == -1) { free(line); line = NULL; } else { add_path(data_chunks, line); if (line != NULL) { free(line); } } } while (line != NULL);
fclose(chunk_file);}3
文件块层次的突变(Smart Mutation Operators)
流程上,higher_order_fuzzing()的主要步骤有如下几个:
1、随机挑选突变因子
2、虚拟结构树结构的线性化收集
3、根据突变因子,从各级数组中随机挑选一个块,进行突变操作
4、调整块起始字节偏移与末尾字节偏移
随机挑选突变因子
u32 r = UR(12); u32 s = 3;
if (model_type == MODEL_PEACH) { if (r <= 5) { if (r <= 1) { s = 0; } else if (r <= 3) { s = 1; } else { s = 2; } } }
switch (s) {
/* 50% chance of no higher-order mutation */ /*han: r = 0 ... 1 -> s = 0, delete chunk, 16.7% chance han: r = 2 ... 3 -> s = 1, splice chunk, 16.7% chance han: r = 4 ... 5 -> s = 2, add chunk, 16.7% chance*/ case 3 ... 5: break;
case 0: { /* Delete chunk */ }break; case 1: { /* Splice chunk */ }break; case 2: { /* Adopt a child from a chunk of the same type */ }break;虚拟结构树节点的线性化收集
void linearize_chunks(struct chunk *c, struct chunk ***first_chunks_arr, struct chunk ***second_chunks_arr, struct chunk ***deeper_chunks_arr, u32 *first_chunks_number, u32 *second_chunks_number, u32 *deeper_chunks_number) { u32 first_level, second_level;
*first_chunks_number = 0; *second_chunks_number = 0; *deeper_chunks_number = 0; *first_chunks_arr = (struct chunk **)malloc((1 << LINEARIZATION_UNIT) * sizeof(struct chunk *)); *second_chunks_arr = (struct chunk **)malloc((1 << LINEARIZATION_UNIT) * sizeof(struct chunk *)); *deeper_chunks_arr = (struct chunk **)malloc((1 << LINEARIZATION_UNIT) * sizeof(struct chunk *)); if (model_type == MODEL_PEACH) { first_level = 1; if (composite_mode) first_level += 2; }
second_level = first_level + 1;
linearize_chunks_recursively(first_level, second_level, c, first_chunks_arr, second_chunks_arr, deeper_chunks_arr, first_chunks_number, second_chunks_number, deeper_chunks_number, 0);}
struct chunk *copy_children_with_new_offset(int new_start_byte, int old_start_byte, struct chunk *c) { struct chunk *sibling = c; struct chunk *ret = NULL;
while (sibling) { struct chunk *children = copy_children_with_new_offset( new_start_byte, old_start_byte, sibling->children);
struct chunk *new = (struct chunk *)malloc(sizeof(struct chunk)); new->id = sibling->id; new->type = sibling->type; new->start_byte = (sibling->start_byte - old_start_byte) + new_start_byte; new->end_byte = (sibling->end_byte - old_start_byte) + new_start_byte; new->modifiable = sibling->modifiable; new->next = ret; new->children = children; ret = new;
sibling = sibling->next; }
return ret;}
void linearize_chunks_recursively( u32 first_level, u32 second_level, struct chunk *c, struct chunk ***first_chunks_arr, struct chunk ***second_chunks_arr, struct chunk ***deeper_chunks_arr, u32 *first_chunks_number, u32 *second_chunks_number, u32 *deeper_chunks_number, u32 depth) { struct chunk *sibling = c;
while (sibling) { linearize_chunks_recursively( first_level, second_level, sibling->children, first_chunks_arr, second_chunks_arr, deeper_chunks_arr, first_chunks_number, second_chunks_number, deeper_chunks_number, depth + 1);
if (depth == first_level) { if (add_chunk_to_array(sibling, first_chunks_arr, first_chunks_number)) return; } else if (depth == second_level) { if (add_chunk_to_array(sibling, second_chunks_arr, second_chunks_number)) return; } else if (depth > second_level) { if (add_chunk_to_array(sibling, deeper_chunks_arr, deeper_chunks_number)) return; } sibling = sibling->next; }}由此可见,linearize_chunks()会根据虚拟结构树中各个chunk的层级,将其指针递归地收集到各级数组中,实现线性化。
倘若在执行命令中加入了-c选项,即启用composite_mode时,AFLSMART将对更深层级的块进行线性化收集工作。
突变操作:块删除(Smart Deletion)
u32 del_from, del_len;struct chunk **first_chunks_array = NULL;struct chunk **second_chunks_array = NULL;struct chunk **deeper_chunks_array = NULL;u32 total_first_chunks = 0;u32 total_second_chunks = 0;u32 total_deeper_chunks = 0;
if (*temp_len < 2) break;
del_from = del_len = 0;linearize_chunks(current_chunk, &first_chunks_array, &second_chunks_array, &deeper_chunks_array, &total_first_chunks, &total_second_chunks, &total_deeper_chunks);if (total_first_chunks <= 0) { if (first_chunks_array != NULL) free(first_chunks_array);
if (second_chunks_array != NULL) free(second_chunks_array);
if (deeper_chunks_array != NULL) free(deeper_chunks_array);
break;}struct chunk *chunk_to_delete = NULL;
/* Make sure we initialize */del_len = 0;
if (total_first_chunks > 1) chunk_to_delete = get_chunk_to_delete( first_chunks_array, total_first_chunks, &del_from, &del_len);
if (first_chunks_array != NULL) free(first_chunks_array);
/* If chunk not found, we try the second-level chunks */if (del_len == 0 && total_second_chunks > 1) { chunk_to_delete = get_chunk_to_delete( second_chunks_array, total_second_chunks, &del_from, &del_len);}
if (second_chunks_array != NULL) free(second_chunks_array);
/* If chunk not found, we try the deeper-level chunks */if (del_len == 0 && total_deeper_chunks > 1) { chunk_to_delete = get_chunk_to_delete( deeper_chunks_array, total_deeper_chunks, &del_from, &del_len);}
if (deeper_chunks_array != NULL) free(deeper_chunks_array);
if (del_len != 0 && del_len < *temp_len) { if (smart_log_mode) { smart_log("BEFORE DELETION:\n"); if (model_type == MODEL_PEACH) smart_log_tree_with_data_hex(current_queue_entry->chunk, (*out_buf));
smart_log("DELETED CHUNK:\n"); smart_log("Type: %d Start: %d End: %d Modifiable: %d\n", chunk_to_delete->type, chunk_to_delete->start_byte, chunk_to_delete->end_byte, chunk_to_delete->modifiable); if (model_type == MODEL_PEACH) smart_log_n_hex(del_len, (*out_buf) + del_from); }struct chunk *get_chunk_to_delete(struct chunk **chunks_array, u32 total_chunks, u32 *del_from, u32 *del_len) { struct chunk *chunk_to_delete = NULL; u8 i;
*del_from = 0; *del_len = 0;
for (i = 0; i < 3; ++i) { int start_byte; u32 chunk_id = UR(total_chunks);
chunk_to_delete = chunks_array[chunk_id]; start_byte = chunk_to_delete->start_byte;
/* It is possible that either the start or the end bytes are unknown (has negative values), so we actually perform the deletion only when these bounds are known. */ if (chunk_to_delete->modifiable && start_byte >= 0 && chunk_to_delete->end_byte >= start_byte) { /* Note that the length to be deleted here is 1 more than end_byte - start_byte, since the end_byte is exactly the position of the last byte, not one more than the last byte. */ *del_from = start_byte; *del_len = chunk_to_delete->end_byte - start_byte + 1; break; } }
return chunk_to_delete;}memmove((*out_buf) + del_from, (*out_buf) + del_from + del_len, (*temp_len) - del_from - del_len + 1);(*temp_len) -= del_len;
current_queue_entry->chunk = search_and_destroy_chunk( current_queue_entry->chunk, chunk_to_delete, del_from, del_len);changed_structure = 1;
if (smart_log_mode) { smart_log("AFTER DELETION:\n"); if (model_type == MODEL_PEACH) smart_log_tree_with_data_hex(current_queue_entry->chunk, (*out_buf));struct chunk *search_and_destroy_chunk(struct chunk *c, struct chunk *target_chunk, int start_byte, unsigned size) { struct chunk *sibling = c; struct chunk *ret = c; struct chunk *prev = NULL;
while (sibling) {
if (sibling == target_chunk) { struct chunk *tmp = sibling->next;
if (ret == sibling) ret = tmp; else prev->next = tmp;
delete_chunks(sibling->children); free(sibling);
reduce_byte_positions(tmp, start_byte, size); sibling = tmp; continue; }
if (sibling->start_byte >= 0 && sibling->start_byte > start_byte) (sibling->start_byte) -= size;
if (sibling->end_byte >= 0 && sibling->end_byte >= start_byte) (sibling->end_byte) -= size;
sibling->children = search_and_destroy_chunk( sibling->children, target_chunk, start_byte, size);
prev = sibling; sibling = sibling->next; }
return ret;}突变操作:同类块替换(Smart Deletion)
struct queue_entry *source_entry;u32 tid;u8 attempts = 20;u32 type, target_len;u32 smart_splicing_with = -1;int target_start_byte = 0;int source_start_byte = 0;struct worklist *source;struct chunk **first_chunks_array = NULL;struct chunk **second_chunks_array = NULL;struct chunk **deeper_chunks_array = NULL;u32 total_first_chunks = 0;u32 total_second_chunks = 0;u32 total_deeper_chunks = 0;
do { tid = UR(queued_paths); smart_splicing_with = tid; source_entry = queue;
while (tid >= 100) { source_entry = source_entry->next_100; tid -= 100; } while (tid--) source_entry = source_entry->next;
while (source_entry && (!source_entry->chunk || source_entry == current_queue_entry)) { source_entry = source_entry->next; smart_splicing_with++; } attempts--;
} while (!source_entry && attempts);
if (attempts == 0) break;struct chunk *target_chunk = get_target_to_splice(first_chunks_array, total_first_chunks, &target_start_byte, &target_len, &type);
if (first_chunks_array != NULL) free(first_chunks_array);
if (target_len <= 0 && total_second_chunks > 0) { target_chunk = get_target_to_splice(second_chunks_array, total_second_chunks, &target_start_byte, &target_len, &type);}
if (second_chunks_array != NULL) free(second_chunks_array);
if (target_len <= 0 && total_deeper_chunks > 0) { target_chunk = get_target_to_splice(deeper_chunks_array, total_deeper_chunks, &target_start_byte, &target_len, &type);}
if (deeper_chunks_array != NULL) free(deeper_chunks_array);struct chunk *get_target_to_splice(struct chunk **chunks_array, u32 total_chunks, int *target_start_byte, u32 *target_len, u32 *type) { struct chunk *target_chunk = NULL; u8 i;
*target_start_byte = 0; *target_len = 0; *type = 0;
for (i = 0; i < 3; ++i) { u32 chunk_id = UR(total_chunks); target_chunk = chunks_array[chunk_id]; *target_start_byte = target_chunk->start_byte;
if (target_chunk->modifiable && *target_start_byte >= 0 && target_chunk->end_byte >= *target_start_byte) { *target_len = target_chunk->end_byte - *target_start_byte + 1; *type = target_chunk->type; break; } }
return target_chunk;}struct worklist *source_init;int same_type_chunks_num = 0;u32 source_len = 0;
/* Find same type and non-bigger size in source */source = get_chunks_of_type(source_entry->chunk, type, target_len, &same_type_chunks_num);source_init = source;
if (source != NULL && same_type_chunks_num > 0) { /* Insert source chunk into out_buf. */ u32 chunk_id = UR(same_type_chunks_num);
source_len = 0; while (source) { if (chunk_id == 0) { source_start_byte = source->chunk->start_byte; if (source_start_byte >= 0 && source->chunk->end_byte >= source_start_byte) { source_len = source->chunk->end_byte - source_start_byte + 1; } break; }
chunk_id--; source = source->next; }1、读取源种子文件,便于获取源块的数据内容,代码如下:
s32 fd;u8 *source_buf;
/* Read the testcase into a new buffer. */
fd = open(source_entry->fname, O_RDONLY);
if (fd < 0) PFATAL("Unable to open '%s'", source_entry->fname);
source_buf = ck_alloc_nozero(source_entry->len);
ck_read(fd, source_buf, source_entry->len, source_entry->fname);
close(fd);s32 fd;u8 *source_buf;
/* Read the testcase into a new buffer. */
fd = open(source_entry->fname, O_RDONLY);
if (fd < 0) PFATAL("Unable to open '%s'", source_entry->fname);
source_buf = ck_alloc_nozero(source_entry->len);
ck_read(fd, source_buf, source_entry->len, source_entry->fname);
close(fd);//han: update the virtual structurestruct chunk *target_next = target_chunk->next;unsigned long target_id = target_chunk->id;delete_chunks(target_chunk->children);target_chunk->children = NULL;reduce_byte_positions(current_queue_entry->chunk, target_start_byte, move_amount);
memcpy(target_chunk, source->chunk, sizeof(struct chunk));target_chunk->id = target_id;target_chunk->start_byte = target_start_byte;target_chunk->end_byte = target_start_byte + source_len - 1;target_chunk->next = target_next;target_chunk->children = copy_children_with_new_offset( target_start_byte, source->chunk->start_byte, source->chunk->children);changed_structure = 1;
/* The source buffer is no longer needed */ck_free(source_buf);突变操作:同类块添加(smart addition)
struct chunk *target_parent_chunk = get_parent_to_insert_child(first_chunks_array, first_total_chunks, &target_start_byte, &target_len, &type);
if (first_chunks_array != NULL) free(first_chunks_array);
if (target_len <= 0 && second_total_chunks > 0) { target_parent_chunk = get_parent_to_insert_child(second_chunks_array, second_total_chunks, &target_start_byte, &target_len, &type);}
if (second_chunks_array != NULL) free(second_chunks_array);
if (target_len <= 0 && deeper_total_chunks > 0) { target_parent_chunk = get_parent_to_insert_child(deeper_chunks_array, deeper_total_chunks, &target_start_byte, &target_len, &type);}
if (deeper_chunks_array != NULL) free(deeper_chunks_array);struct chunk *get_parent_to_insert_child(struct chunk **chunks_array, u32 total_chunks, int *target_start_byte, u32 *target_len, u32 *type) { struct chunk *target_parent_chunk = NULL;
*target_start_byte = 0; *target_len = 0; *type = 0;
for (u8 i = 0; i < 3; ++i) { u32 chunk_id = UR(total_chunks); target_parent_chunk = chunks_array[chunk_id]; *target_start_byte = target_parent_chunk->start_byte; if (*target_start_byte >= 0 && target_parent_chunk->end_byte >= *target_start_byte && target_parent_chunk->children != NULL) { *target_len = target_parent_chunk->end_byte - *target_start_byte + 1; *type = target_parent_chunk->type; break; } }
return target_parent_chunk;}1、读取源种子文件,便于获取尾块的数据内容,代码如下:
/* Read the testcase into a new buffer. */
fd = open(source_entry->fname, O_RDONLY);
if (fd < 0) PFATAL("Unable to open '%s'", source_entry->fname);
source_buf = ck_alloc_nozero(source_entry->len);
ck_read(fd, source_buf, source_entry->len, source_entry->fname);
close(fd);/* Move the data around */if (target_parent_chunk->end_byte + 1 < *temp_len) { memmove((*out_buf) + target_parent_chunk->end_byte + source_child_chunk_size + 1, (*out_buf) + target_parent_chunk->end_byte + 1, *temp_len - (target_parent_chunk->end_byte + 1));}memcpy((*out_buf) + target_parent_chunk->end_byte + 1, source_buf + source_child_chunk->start_byte, source_child_chunk_size);
*temp_len += source_child_chunk_size;/* Move the data around */if (target_parent_chunk->end_byte + 1 < *temp_len) { memmove((*out_buf) + target_parent_chunk->end_byte + source_child_chunk_size + 1, (*out_buf) + target_parent_chunk->end_byte + 1, *temp_len - (target_parent_chunk->end_byte + 1));}memcpy((*out_buf) + target_parent_chunk->end_byte + 1, source_buf + source_child_chunk->start_byte, source_child_chunk_size);
*temp_len += source_child_chunk_size;最终实现的效果如其论文中所示的一致:
4
基于有效性的种子能量调度(Valid-based Power Schedule)
相较于传统的对文件内容进行比特、字节级别突变的fuzzer,AFLSMART实现的块级突变还是挺有意思的,在此写下自己的一些学习总结。
AFLSMART项目地址:https://github.com/aflsmart/aflsmart
AFLSMART文章:https://thuanpv.github.io/publications/TSE19_aflsmart.pdf
看雪ID:gaveu屯烫烫
https://bbs.pediy.com/user-home-777374.htm
# 往期推荐
1.通过ObRegisterCallbacks学习对象监控与反对象监控
5.通过CmRegisterCallback学习注册表监控与反注册表监控
6.Android APP漏洞之战——权限安全和安全配置漏洞详解
球分享
球点赞
球在看
点击“阅读原文”,了解更多!