bool emu_graph_path_exists(struct emu_graph *eg, struct emu_vertex *from, struct emu_vertex *to) { struct emu_vertex *it; struct emu_vertex *ev; struct emu_queue *eq; for ( it = emu_vertexes_first(eg->vertexes); !emu_vertexes_attail(it); it = emu_vertexes_next(it) ) { it->color = white; it->distance = 0; } it = from; eq = emu_queue_new(); emu_queue_enqueue(eq, from); while ( emu_queue_empty(eq) == false ) { struct emu_edge *ee; ev = (struct emu_vertex *)emu_queue_dequeue(eq); if ( ev == to ) { emu_queue_free(eq); return true; } for ( ee = emu_edges_first(ev->edges); !emu_edges_attail(ee); ee = emu_edges_next(ee) ) { if ( ee->destination->color != white ) continue; ee->destination->distance = ev->distance + 1; ee->destination->color = grey; emu_queue_enqueue(eq, ee->destination); } ev->color = black; } emu_queue_free(eq); return false; }
/** * This function takes the emu, the offset and tries to run * steps iterations. If it fails due to uninitialized * registers/eflags it will try to use the information passed by * etas to traverse the instruction tree and find an instruction * path in the tree which satisfies the initialization * requirements. * * To avoid testing the same positions over and over, the * already-tested positions are held in the known_positions * hashtable. * * The result is returned in the tested_positions_list. * * * The function is called for every GetPC candidate in the * suspected shellcode, therefore the known_positions prevent * testing the same locations for different starting points in * the data too. * * It is the vital part of libemu's shellcode detection, hard to * understand, hard to improve and hard to fix. * * Messing this function up, destroys libemu's shellcode * detection. * * The function is a mess, given the complexity of the loops it * is too long and the variable names do not provide any help. * * The bruteforce flag is useful, as it allows bfs even if some * instructions initializations are not set properly, but you'll * definitely miss its impact on the behaviour when making a * guess why something does not work. * * short explanation of the logic: * * the first starting point is our offset * * while we have starting points: * run the shellcode: error? * no! * continue * yes! * use the current starting eip as starting point to bfs * look for instructions which satisfy the requirements OR * brutefore * queue these new instructions as starting points * * * * History has proven the the function to be susceptible to * denial of service attacks, running the system out of memory * or cycles. * So, if you experience a 'problem', this is the first place to * look at, and the last one you want to look at, if you want to * cause a 'problem', same rules apply. * * This comment was written when patching one of these dos * problems * The known_positions arg has been unused for the time before, * seems like there was a good idea when writing the function * initially, but this good idea was abandoned once 'it worked' * * * * @param e the emu to run * @param data the data we run * @param datasize the data size * @param eipoffset the offset for eip * @param steps how many steps to try running * @param etas the track and source tree - the substantial * information to run the breath first search * @param known_positions * already tested positions * @param stats_tested_positions_list * the result list * @param brute_force * be aggressive? * * @return */ int32_t emu_shellcode_run_and_track(struct emu *e, uint8_t *data, uint16_t datasize, uint16_t eipoffset, uint32_t steps, // struct emu_env_w32 *env, struct emu_track_and_source *etas, struct emu_hashtable *known_positions, struct emu_list_root *stats_tested_positions_list, bool brute_force ) { struct emu_cpu *cpu = emu_cpu_get(e); struct emu_memory *mem = emu_memory_get(e); struct emu_queue *eq = emu_queue_new(); emu_queue_enqueue(eq, (void *)((uintptr_t)(uint32_t)eipoffset+STATIC_OFFSET)); // struct emu_list_root *tested_positions = emu_list_create(); struct emu_env *env = NULL; { // mark all vertexes white struct emu_vertex *x; for ( x= emu_vertexes_first(etas->static_instr_graph->vertexes); !emu_vertexes_attail(x); x = emu_vertexes_next(x)) { x->color = white; } } while ( !emu_queue_empty(eq) ) { uint32_t current_offset = (uint32_t)(uintptr_t)emu_queue_dequeue(eq); /* init the cpu/memory * scooped to keep number of used varnames small */ { logDebug(e, "running at offset %i %08x\n", current_offset, current_offset); emu_memory_clear(mem); if (env) emu_env_free(env); /* write the code to the offset */ emu_memory_write_block(mem, STATIC_OFFSET, data, datasize); env = emu_env_new(e); /* set the registers to the initial values */ int reg; for ( reg=0;reg<8;reg++ ) emu_cpu_reg32_set(cpu,reg ,0x0); emu_cpu_reg32_set(cpu, esp, 0x00120000); emu_cpu_eip_set(cpu, current_offset); /* set the flags */ emu_cpu_eflags_set(cpu,0x0); cpu->tracking = etas; } emu_tracking_info_clear(&etas->track); int j; for ( j=0;j<steps;j++ ) { // emu_cpu_debug_print(cpu); uint32_t eipsave; eipsave = emu_cpu_eip_get(cpu); struct emu_env_hook *hook = NULL; hook = emu_env_w32_eip_check(env); if ( hook != NULL ) { if ( hook->hook.win->fnhook == NULL ) break; } else { int32_t ret = emu_cpu_parse(emu_cpu_get(e)); if ( ret == -1 ) { logDebug(e, "error at %s\n", cpu->instr_string); break; } ret = emu_cpu_step(emu_cpu_get(e)); if ( ret == -1 ) { logDebug(e, "error at %s (%s)\n", cpu->instr_string, strerror(emu_errno(e))); if (brute_force) { logDebug(e, "goto traversal\n"); goto traversal; } else break; } if ( emu_track_instruction_check(e, etas) == -1 ) { traversal: logDebug(e, "failed instr %s\n", cpu->instr_string); logDebug(e, "tracking at eip %08x\n", eipsave); if ( 0 && cpu->instr.is_fpu ) { } else { /* save the requirements of the failed instruction */ // struct emu_tracking_info *instruction_needs_ti = emu_tracking_info_new(); // emu_tracking_info_copy(&cpu->instr.cpu.track.need, instruction_needs_ti); struct emu_queue *bfs_queue = emu_queue_new(); /* * the current starting point is the first position used to bfs * scooped to avoid varname collisions */ { struct emu_tracking_info *eti = emu_tracking_info_new(); emu_tracking_info_diff(&cpu->instr.track.need, &etas->track, eti); eti->eip = current_offset; emu_tracking_info_debug_print(eti); if( emu_hashtable_search(known_positions, (void *)(uintptr_t)(uint32_t)current_offset) != NULL) { logDebug(e, "Known %p %x\n", eti, eti->eip); emu_tracking_info_free(eti); emu_queue_free(bfs_queue); break; } emu_queue_enqueue(bfs_queue, eti); } while ( !emu_queue_empty(bfs_queue) ) { logDebug(e, "loop %s:%i\n", __FILE__, __LINE__); struct emu_tracking_info *current_pos_ti_diff = (struct emu_tracking_info *)emu_queue_dequeue(bfs_queue); struct emu_hashtable_item *current_pos_ht = emu_hashtable_search(etas->static_instr_table, (void *)(uintptr_t)(uint32_t)current_pos_ti_diff->eip); if (current_pos_ht == NULL) { logDebug(e, "current_pos_ht is NULL?\n"); exit(-1); } struct emu_vertex *current_pos_v = (struct emu_vertex *)current_pos_ht->value; struct emu_source_and_track_instr_info *current_pos_satii = (struct emu_source_and_track_instr_info *)current_pos_v->data; if( emu_hashtable_search(known_positions, (void *)(uintptr_t)(uint32_t)current_pos_satii->eip) != NULL ) { logDebug(e, "Known Again %p %x\n", current_pos_satii, current_pos_satii->eip); current_pos_v->color = red; emu_tracking_info_free(current_pos_ti_diff); continue; } if (current_pos_v->color == red) { logDebug(e, "is red %p %x: %s\n", (uintptr_t)current_pos_v, current_pos_satii->eip, current_pos_satii->instrstring); emu_tracking_info_free(current_pos_ti_diff); continue; } logDebug(e, "marking red %p %x: %s \n", (uintptr_t)current_pos_v, current_pos_satii->eip, current_pos_satii->instrstring); current_pos_v->color = red; emu_hashtable_insert(known_positions, (void *)(uintptr_t)(uint32_t)current_pos_satii->eip, NULL); while ( !emu_tracking_info_covers(¤t_pos_satii->track.init, current_pos_ti_diff) || brute_force ) { logDebug(e, "loop %s:%i\n", __FILE__, __LINE__); if ( current_pos_v->backlinks == 0 ) { break; } else if ( current_pos_v->backlinks > 1 ) { /* queue all to diffs to the bfs queue */ struct emu_edge *ee; struct emu_vertex *ev; for ( ee = emu_edges_first(current_pos_v->backedges); !emu_edges_attail(ee); ee=emu_edges_next(ee) ) { ev = ee->destination; /** * ignore positions we've visited already * avoids dos for jz 0 * * try the next position instead */ if( ev->color == red ) continue; struct emu_source_and_track_instr_info *next_pos_satii = (struct emu_source_and_track_instr_info *)ev->data; logDebug(e, "EnqueueLoop %p %x %s\n", next_pos_satii, next_pos_satii->eip, next_pos_satii->instrstring); struct emu_tracking_info *eti = emu_tracking_info_new(); emu_tracking_info_diff(current_pos_ti_diff, ¤t_pos_satii->track.init, eti); eti->eip = next_pos_satii->eip; emu_queue_enqueue(bfs_queue, eti); } /** * the new possible positions and requirements got queued into the bfs queue, * we break here, so the new queued positions can try to work it out */ break; } else if ( current_pos_v->backlinks == 1 ) { /* follow the single link */ /** * ignore loops to self * avoids dos for "\xe3\xfe\xe8" * breaks the upper loop * */ if( current_pos_v == emu_edges_first(current_pos_v->backedges)->destination ) break; current_pos_v = emu_edges_first(current_pos_v->backedges)->destination; /** * again, ignore already visited positions * breaks the upper loop */ if( current_pos_v->color == red ) break; current_pos_v->color = red; struct emu_source_and_track_instr_info *next_pos_satii = (struct emu_source_and_track_instr_info *)current_pos_v->data; logDebug(e, "FollowSingle %p %i %x %s\n", next_pos_satii, current_pos_v->color, next_pos_satii->eip, next_pos_satii->instrstring); current_pos_satii = (struct emu_source_and_track_instr_info *)current_pos_v->data; emu_tracking_info_diff(current_pos_ti_diff, ¤t_pos_satii->track.init, current_pos_ti_diff); } } if ( emu_tracking_info_covers(¤t_pos_satii->track.init, current_pos_ti_diff) || brute_force ) { /** * we have a new starting point, this starting point may fail * too - if further backwards traversal is required * therefore we mark it white, so it can be processed again */ logDebug(e, "found position which satiesfies the requirements %i %08x\n", current_pos_satii->eip, current_pos_satii->eip); current_pos_ht = emu_hashtable_search(etas->static_instr_table, (void *)(uintptr_t)(uint32_t)current_pos_satii->eip); current_pos_v = (struct emu_vertex *)current_pos_ht->value; if(current_pos_satii->eip != current_offset ) { logDebug(e, "marking white %p %x: %s \n", (uintptr_t)current_pos_v, current_pos_satii->eip, current_pos_satii->instrstring); current_pos_v->color = white; } emu_tracking_info_debug_print(¤t_pos_satii->track.init); emu_queue_enqueue(eq, (void *)((uintptr_t)(uint32_t)current_pos_satii->eip)); } //discard: emu_tracking_info_free( current_pos_ti_diff); } emu_queue_free(bfs_queue); } /** * the shellcode did not run correctly as he was missing instructions initializing required registers * we did what we could do in the prev lines of code to find better offsets to start from * the working offsets got queued into the main queue, so we break here to give them a chance */ break; }else { logDebug(e, "%s\n", cpu->instr_string); } } } struct emu_stats *es = emu_stats_new(); es->eip = current_offset; es->cpu.steps = j; struct emu_list_item *eli = emu_list_item_create(); eli->data = es; logDebug(e, "INSERT %i %x steps %i\n", current_offset, current_offset, j); emu_list_insert_last(stats_tested_positions_list, eli); } emu_queue_free(eq); emu_env_free(env); /* sort all tested positions by the number of steps ascending */ emu_list_qsort(stats_tested_positions_list, tested_positions_cmp); struct emu_list_item *eli = emu_list_first(stats_tested_positions_list); struct emu_stats *es = (struct emu_stats *)eli->data; uint32_t best_offset = es->eip; return best_offset - STATIC_OFFSET; }
bool emu_graph_loop_detect(struct emu_graph *eg, struct emu_vertex *from) { struct emu_vertex *it; struct emu_vertex *ev; struct emu_queue *eq; for ( it = emu_vertexes_first(eg->vertexes); !emu_vertexes_attail(it); it = emu_vertexes_next(it) ) it->color = white; it = from; eq = emu_queue_new(); emu_queue_enqueue(eq, from); while ( emu_queue_empty(eq) == false ) { struct emu_edge *ee; ev = (struct emu_vertex *)emu_queue_dequeue(eq); for ( ee = emu_edges_first(ev->edges); !emu_edges_attail(ee); ee = emu_edges_next(ee) ) { if ( ee->destination->color != white ) continue; ee->destination->color = grey; emu_queue_enqueue(eq, ee->destination); } ev->color = black; } for ( it = emu_vertexes_first(eg->vertexes); !emu_vertexes_attail(it); it = emu_vertexes_next(it) ) { // printf("%08x \n\tcolor %i\n\tedges %i\n\tpath %i\n",((struct emu_source_and_track_instr_info *)it->data)->eip, it->color, emu_edges_length(it->edges), (int)emu_graph_path_exists(eg, from, it)); if (it->color == white) continue; // if (emu_edges_length(it->edges) < 2) // continue; /* if (emu_graph_path_exists(eg, from, it) == false) continue; */ /* printf("%08x => %08x\n", ((struct emu_source_and_track_instr_info *)from->data)->eip, ((struct emu_source_and_track_instr_info *)it->data)->eip); */ emu_queue_enqueue(eq, it); } while ( emu_queue_empty(eq) == false ) { struct emu_edge *ee; ev = (struct emu_vertex *)emu_queue_dequeue(eq); for ( ee = emu_edges_first(ev->edges); !emu_edges_attail(ee); ee = emu_edges_next(ee) ) { if ( emu_graph_path_exists(eg, ee->destination, ev) == true ) { emu_queue_free(eq); return true; } } } emu_queue_free(eq); return false; }