int rdis_function_reachable (struct _rdis * rdis, uint64_t address) { struct _function * function = map_fetch(rdis->functions, address); if (function == NULL) return -1; function->flags |= FUNCTION_REACHABLE; // get this function's call graph struct _graph * cg = create_call_graph(rdis->graph, address); struct _graph_it * git; // for each function in the graph for (git = graph_iterator(cg); git != NULL; git = graph_it_next(git)) { struct _list * ins_list = graph_it_data(git); struct _list_it * lit; // for each call in the function in the graph for (lit = list_iterator(ins_list); lit != NULL; lit = lit->next) { struct _ins * ins = lit->data; // insure function has correct flags if ( (ins->flags & (INS_TARGET_SET | INS_CALL)) == (INS_TARGET_SET | INS_CALL)) { function = map_fetch(rdis->functions, ins->target); if (function == NULL) continue; function->flags |= FUNCTION_REACHABLE; } } } return 0; }
struct _map * rdis_g_references (struct _rdis * rdis) { struct _map * references = map_create(); struct _graph_it * git; // for each node for (git = graph_iterator(rdis->graph); git != NULL; git = graph_it_next(git)) { struct _graph_node * node = graph_it_node(git); struct _list_it * lit; // for each instruction for (lit = list_iterator(node->data); lit != NULL; lit = lit->next) { struct _ins * ins = lit->data; struct _list_it * rit; // for each reference for (rit = list_iterator(ins->references); rit != NULL; rit = rit->next) { struct _reference * reference = rit->data; int delete_reference = 0; if (reference->type == REFERENCE_CONSTANT) { uint64_t lower = map_fetch_max_key(rdis->memory, reference->address); struct _buffer * buffer = map_fetch(rdis->memory, lower); if (buffer == NULL) continue; uint64_t upper = lower + buffer->size; if ( (reference->address < lower) || (reference->address >= upper)) continue; reference = object_copy(reference); reference->type = REFERENCE_CONSTANT_ADDRESSABLE; delete_reference = 1; } struct _list * ref_list = map_fetch(references, reference->address); if (ref_list == NULL) { ref_list = list_create(); map_insert(references, reference->address, ref_list); object_delete(ref_list); ref_list = map_fetch(references, reference->address); } list_append(ref_list, reference); if (delete_reference) object_delete(reference); } } } return references; }
void rdis_check_references (struct _rdis * rdis) { struct _graph_it * git; // for each node for (git = graph_iterator(rdis->graph); git != NULL; git = graph_it_next(git)) { struct _graph_node * node = graph_it_node(git); struct _list_it * lit; // for each instruction for (lit = list_iterator(node->data); lit != NULL; lit = lit->next) { struct _ins * ins = lit->data; struct _list_it * rit; // for each reference for (rit = list_iterator(ins->references); rit != NULL; rit = rit->next) { struct _reference * reference = rit->data; if (reference->type == REFERENCE_CONSTANT) { uint64_t lower = map_fetch_max_key(rdis->memory, reference->address); struct _buffer * buffer = map_fetch(rdis->memory, lower); if (buffer == NULL) continue; uint64_t upper = lower + buffer->size; if ( (reference->address < lower) || (reference->address >= upper)) continue; reference->type = REFERENCE_CONSTANT_ADDRESSABLE; } } } } }
struct _map * elf32_functions_wqueue (struct _elf32 * elf32, struct _map * memory, struct _list * entries) { struct _map * functions = map_create(); struct _wqueue * wqueue = wqueue_create(); struct _list_it * it; for (it = list_iterator(entries); it != NULL; it = it->next) { struct _function * function = it->data; if (map_fetch(functions, function->address) == NULL) map_insert(functions, function->address, function); struct _x86_wqueue * x86w; x86w = x86_wqueue_create(function->address, memory); wqueue_push(wqueue, WQUEUE_CALLBACK(x86_functions_wqueue), x86w); object_delete(x86w); } wqueue_wait(wqueue); struct _map * fmap; while ((fmap = wqueue_peek(wqueue)) != NULL) { struct _map_it * mit; for (mit = map_iterator(fmap); mit != NULL; mit = map_it_next(mit)) { struct _function * function = map_it_data(mit); if (map_fetch(functions, function->address) == NULL) map_insert(functions, function->address, function); } wqueue_pop(wqueue); } object_delete(wqueue); return functions; }
int rdis_function_bounds (struct _rdis * rdis, uint64_t address) { struct _function * function = map_fetch(rdis->functions, address); if (function == NULL) return -1; struct _graph * family = graph_family(rdis->graph, address); if (family == NULL) return -1; uint64_t lower = -1; uint64_t upper = 0; struct _graph_it * it; for (it = graph_iterator(family); it != NULL; it = graph_it_next(it)) { struct _graph_node * node = graph_it_node(it); struct _list * ins_list = node->data; struct _list_it * iit; for (iit = list_iterator(ins_list); iit != NULL; iit = iit->next) { struct _ins * ins = iit->data; if (ins->address < lower) lower = ins->address; if (ins->address + ins->size > upper) upper = ins->address + ins->size; } } object_delete(family); function->bounds.lower = lower; function->bounds.upper = upper; return 0; }
int rdis_update_memory (struct _rdis * rdis, uint64_t address, struct _buffer * buffer) { if (buffer == NULL) return -1; mem_map_set (rdis->memory, address, buffer); // we will regraph functions whose bounds fall within this updated memory, // and all functions whose bounds fall within the bounds of functions to be // regraphed (step 2 simplifies things later on) struct _queue * queue = queue_create(); struct _map_it * it; for (it = map_iterator(rdis->functions); it != NULL; it = map_it_next(it)) { struct _function * function = map_it_data(it); if ( ( (function->bounds.lower >= address) && (function->bounds.lower < address + buffer->size)) || ( (function->bounds.upper >= address) && (function->bounds.upper < address + buffer->size)) || ( (function->bounds.lower <= address) && (function->bounds.upper >= address + buffer->size))) { queue_push(queue, function); } } struct _map * regraph_functions = map_create(); while (queue->size > 0) { struct _function * function = queue_peek(queue); if (map_fetch(regraph_functions, function->address) != NULL) { queue_pop(queue); continue; } printf("adding regraph function %llx\n", (unsigned long long) function->address); map_insert(regraph_functions, function->address, function); for (it = map_iterator(rdis->functions); it != NULL; it = map_it_next(it)) { struct _function * cmp_function = map_it_data(it); if ( ( (cmp_function->bounds.lower >= function->bounds.lower) && (cmp_function->bounds.lower < function->bounds.upper)) || ( (cmp_function->bounds.upper >= function->bounds.lower) && (cmp_function->bounds.upper < function->bounds.upper)) || ( (cmp_function->bounds.lower <= function->bounds.lower) && (cmp_function->bounds.upper >= function->bounds.upper))) queue_push(queue, cmp_function); } } // regraph dem functions struct _graph * new_graph; new_graph = loader_graph_functions(rdis->loader, rdis->memory, regraph_functions); // We are now going to go through all nodes in our regraph functions. We // will copy over comments to the new instructions and then remove the // regraph function nodes from the original graph for (it = map_iterator(regraph_functions); it != NULL; it = map_it_next(it)) { struct _function * function = map_it_data(it); struct _graph * family = graph_family(rdis->graph, function->address); if (family == NULL) continue; struct _graph_it * git; for (git = graph_iterator(family); git != NULL; git = graph_it_next(git)) { struct _list * ins_list = graph_it_data(git); struct _list_it * iit; for (iit = list_iterator(ins_list); iit != NULL; iit = iit->next) { struct _ins * ins = iit->data; if (ins->comment == NULL) continue; struct _ins * new_ins = graph_fetch_ins(new_graph, ins->address); if (ins->size != new_ins->size) continue; if (memcmp(ins->bytes, new_ins->bytes, ins->size) == 0) { printf("copy over comment from instruction at %llx\n", (unsigned long long) ins->address); ins_s_comment(new_ins, ins->comment); } } // add node for deletion struct _index * index = index_create(graph_it_index(git)); queue_push(queue, index); object_delete(index); } object_delete(family); while (queue->size > 0) { struct _index * index = queue_peek(queue); graph_remove_node(rdis->graph, index->index); queue_pop(queue); } } // merge the new graph with the old graph graph_merge(rdis->graph, new_graph); // reset bounds of these functions for (it = map_iterator(regraph_functions); it != NULL; it = map_it_next(it)) { struct _function * function = map_it_data(it); rdis_function_bounds(rdis, function->address); } objects_delete(queue, new_graph, regraph_functions, NULL); rdis_callback(rdis, RDIS_CALLBACK_ALL); return 0; }
int rdis_user_function (struct _rdis * rdis, uint64_t address) { // get a tree of all functions reachable at this address struct _map * functions = loader_function_address(rdis->loader, rdis->memory, address); // add in this address as a new function as well struct _function * function = function_create(address); map_insert(functions, function->address, function); object_delete(function); // for each newly reachable function struct _map_it * mit; for (mit = map_iterator(functions); mit != NULL; mit = map_it_next(mit)) { struct _function * function = map_it_data(mit); uint64_t fitaddress = function->address; // if we already have this function, skip it if (map_fetch(rdis->functions, function->address) != NULL) continue; // add this function to the rdis->function_tree map_insert(rdis->functions, function->address, function); // add label struct _label * label = loader_label_address(rdis->loader, rdis->memory, fitaddress); map_insert(rdis->labels, fitaddress, label); object_delete(label); // if this function is already in our graph, all we need to do is make // sure its a separate node and then remove function predecessors struct _graph_node * node = graph_fetch_node_max(rdis->graph, fitaddress); if (node != NULL) { // already a node, remove function predecessors if (node->index == address) { remove_function_predecessors(rdis->graph, rdis->functions); continue; } // search for instruction with given address struct _list * ins_list = node->data; struct _list_it * it; for (it = list_iterator(ins_list); it != NULL; it = it->next) { struct _ins * ins = it->data; // found instruction if (ins->address == fitaddress) { // create a new instruction list. // add remaining instructions to new list while removing them from // the current list struct _list * new_ins_list = list_create(); while (1) { list_append(new_ins_list, it->data); it = list_remove(ins_list, it); if (it == NULL) break; } // create a new graph node for this new function graph_add_node(rdis->graph, fitaddress, new_ins_list); // all graph successors from old node are added to new node struct _queue * queue = queue_create(); struct _list * successors = graph_node_successors(node); struct _list_it * sit; for (sit = list_iterator(successors); sit != NULL; sit = sit->next) { struct _graph_edge * edge = sit->data; graph_add_edge(rdis->graph, fitaddress, edge->tail, edge->data); queue_push(queue, edge); } object_delete(successors); // and removed from old node while (queue->size > 0) { struct _graph_edge * edge = queue_peek(queue); graph_remove_edge(rdis->graph, edge->head, edge->tail); queue_pop(queue); } object_delete(queue); // that was easy break; } } if (it != NULL) continue; } // we need to create a new graph for this node struct _graph * graph = loader_graph_address(rdis->loader, rdis->memory, fitaddress); graph_merge(rdis->graph, graph); object_delete(graph); } object_delete(functions); rdis_callback(rdis, RDIS_CALLBACK_ALL); return 0; }
void x8664_functions_r (struct _map * functions, struct _tree * disassembled, uint64_t address, struct _map * memory) { ud_t ud_obj; int continue_disassembling = 1; struct _buffer * buffer = map_fetch_max(memory, address); if (buffer == NULL) return; uint64_t base_address = map_fetch_max_key(memory, address); if (base_address + buffer->size < address) return; uint64_t offset = address - base_address; ud_init (&ud_obj); ud_set_mode (&ud_obj, 64); ud_set_syntax(&ud_obj, UD_SYN_INTEL); ud_set_input_buffer(&ud_obj, &(buffer->bytes[offset]), buffer->size - offset); while (continue_disassembling == 1) { size_t bytes_disassembled = ud_disassemble(&ud_obj); if (bytes_disassembled == 0) { break; } if ( (ud_obj.mnemonic == UD_Icall) && (ud_obj.operand[0].type == UD_OP_JIMM)) { uint64_t target_addr = address + ud_insn_len(&ud_obj) + udis86_sign_extend_lval(&(ud_obj.operand[0])); if (map_fetch(functions, target_addr) == NULL) { struct _function * function = function_create(target_addr); map_insert(functions, target_addr, function); object_delete(function); } } struct _index * index = index_create(address); if (tree_fetch(disassembled, index) != NULL) { object_delete(index); return; } tree_insert(disassembled, index); object_delete(index); // these mnemonics cause us to continue disassembly somewhere else struct ud_operand * operand; switch (ud_obj.mnemonic) { case UD_Ijo : case UD_Ijno : case UD_Ijb : case UD_Ijae : case UD_Ijz : case UD_Ijnz : case UD_Ijbe : case UD_Ija : case UD_Ijs : case UD_Ijns : case UD_Ijp : case UD_Ijnp : case UD_Ijl : case UD_Ijge : case UD_Ijle : case UD_Ijg : case UD_Ijmp : case UD_Iloop : case UD_Icall : operand = &(ud_obj.operand[0]); if (operand->type == UD_OP_JIMM) { x8664_functions_r(functions, disassembled, address + ud_insn_len(&ud_obj) + udis86_sign_extend_lval(operand), memory); } break; default : break; } // these mnemonics cause disassembly to stop switch (ud_obj.mnemonic) { case UD_Iret : case UD_Ihlt : case UD_Ijmp : continue_disassembling = 0; break; default : break; } address += bytes_disassembled; } }
struct _map * elf32_functions (struct _elf32 * elf32, struct _map * memory) { struct _list * entries = list_create(); // add the entry point struct _function * function = function_create(elf32_entry(elf32)); list_append(entries, function); object_delete(function); // check for __libc_start_main loader uint64_t target_offset = elf32_entry(elf32) - elf32_base_address(elf32) + 0x17; if (target_offset + 0x10 < elf32->data_size) { uint8_t * data = &(elf32->data[target_offset]); size_t size = elf32->data_size - target_offset; ud_t ud_obj; ud_init (&ud_obj); ud_set_mode (&ud_obj, 32); ud_set_syntax(&ud_obj, UD_SYN_INTEL); ud_set_input_buffer(&ud_obj, data, size); ud_disassemble(&ud_obj); if (ud_obj.mnemonic == UD_Ipush) { printf("found __libc_start_main loader, main at %llx\n", (unsigned long long) udis86_sign_extend_lval(&(ud_obj.operand[0]))); // add main to function tree struct _function * function; function = function_create(udis86_sign_extend_lval(&(ud_obj.operand[0]))); list_append(entries, function); object_delete(function); } else printf("disassembled: %s\n disassembled at %llx\n", ud_insn_asm(&ud_obj), (unsigned long long) target_offset); } struct _map * functions = elf32_functions_wqueue(elf32, memory, entries); // these are the reachable functions struct _map_it * mit; for (mit = map_iterator(functions); mit != NULL; mit = map_it_next(mit)) { struct _function * function = map_it_data(mit); function->flags |= FUNCTION_REACHABLE; } // reset entries object_delete(entries); entries = list_create(); // symbols are easy int sec_i; for (sec_i = 0; sec_i < elf32->ehdr->e_shnum; sec_i++) { Elf32_Shdr * shdr = elf32_shdr(elf32, sec_i); if (shdr == NULL) break; if ((shdr->sh_type != SHT_SYMTAB) && (shdr->sh_type != SHT_DYNSYM)) continue; int sym_i; for (sym_i = 0; sym_i < shdr->sh_size / shdr->sh_entsize; sym_i++) { Elf32_Sym * sym = elf32_section_element(elf32, sec_i, sym_i); if (sym == NULL) break; if (ELF32_ST_TYPE(sym->st_info) != STT_FUNC) continue; if (sym->st_value == 0) continue; struct _function * function = function_create(sym->st_value); list_append(entries, function); object_delete(function); } } struct _map * sym_functions = elf32_functions_wqueue(elf32, memory, entries); for (mit = map_iterator(sym_functions); mit != NULL; mit = map_it_next(mit)) { struct _function * function = map_it_data(mit); if (map_fetch(functions, function->address) == NULL) map_insert(functions, function->address, function); } object_delete(sym_functions); object_delete(entries); return functions; }
struct _graph * redis_x86_graph (uint64_t address, struct _map * memory) { uint64_t next_index = 1; uint64_t this_index = 0; uint64_t last_index = 0; struct _redis_x86 * redis_x86 = redis_x86_create(); redis_x86_mem_from_mem_map(redis_x86, memory); redis_x86->regs[RED_EIP] = address; redis_x86_false_stack(redis_x86); struct _map * ins_map = map_create(); struct _graph * graph = graph_create(); while (redis_x86_step(redis_x86) == REDIS_SUCCESS) { uint64_t address = redis_x86->ins_addr; // do we have an instruction at this address already? struct _index * index = map_fetch(ins_map, address); // we have an instruction, fetch it, make sure it matches if (index) { struct _graph_node * node = graph_fetch_node(graph, index->index); struct _ins * ins = list_first(node->data); // instructions diverge, create new instruction if ( (ins->size != redis_x86->ins_size) || (memcmp(ins->bytes, redis_x86->ins_bytes, redis_x86->ins_size))) { ins = redis_x86_create_ins(redis_x86); if (ins == NULL) { fprintf(stderr, "could not create ins, eip=%llx\n", (unsigned long long) redis_x86->ins_addr); break; } struct _list * list = list_create(); list_append(list, ins); object_delete(ins); graph_add_node(graph, next_index, list); object_delete(list); map_remove(ins_map, redis_x86->ins_addr); index = index_create(next_index++); map_insert(ins_map, redis_x86->ins_addr, index); this_index = index->index; object_delete(index); } else this_index = index->index; } // no instruction at this address, create it else { struct _ins * ins = redis_x86_create_ins(redis_x86); if (ins == NULL) { fprintf(stderr, "could not create ins eip=%llx\n", (unsigned long long) redis_x86->ins_addr); break; } struct _list * list = list_create(); list_append(list, ins); object_delete(ins); graph_add_node(graph, next_index, list); object_delete(list); index = index_create(next_index++); map_insert(ins_map, redis_x86->ins_addr, index); this_index = index->index; object_delete(index); } /* * create an edge from last index to this index * because our graph library enforces both the condition that the head * and tail nodes are valid, and that there exists only one edge per * head->tail combination, we can blindly add edges here and let the * graph library work out the details */ printf("[edge] %llx -> %llx\n", (unsigned long long) last_index, (unsigned long long) this_index); struct _ins_edge * ins_edge = ins_edge_create(INS_EDGE_NORMAL); graph_add_edge(graph, last_index, this_index, ins_edge); object_delete(ins_edge); last_index = this_index; printf("%llx -> %llx\n", (unsigned long long) last_index, (unsigned long long) this_index); } object_delete(ins_map); object_delete(redis_x86); return graph; }
struct _graph * recursive_disassemble (const struct _map * mem_map, uint64_t entry, struct _ins * (* ins_callback) (const struct _map *, uint64_t)) { struct _queue * queue = queue_create(); struct _map * map = map_create(); struct _index * index = index_create(entry); queue_push(queue, index); object_delete(index); while (queue->size > 0) { struct _index * index = queue_peek(queue); if (map_fetch(map, index->index)) { queue_pop(queue); continue; } struct _ins * ins = ins_callback(mem_map, index->index); if (ins == NULL) { queue_pop(queue); continue; } map_insert(map, index->index, ins); struct _list_it * lit; for (lit = list_iterator(ins->successors); lit != NULL; lit = lit->next) { struct _ins_value * successor = lit->data; if (successor->type == INS_SUC_CALL) continue; struct _index * index = index_create(successor->address); queue_push(queue, index); object_delete(index); } queue_pop(queue); } object_delete(queue); // create graph nodes struct _graph * graph = graph_create(); struct _map_it * mit; for (mit = map_iterator(map); mit != NULL; mit = map_it_next(mit)) { graph_add_node(graph, map_it_key(mit), map_it_data(mit)); } // create graph edges for (mit = map_iterator(map); mit != NULL; mit = map_it_next(mit)) { struct _ins * ins = map_it_data(mit); struct _list_it * lit; for (lit = list_iterator(ins->successors); lit != NULL; lit = lit->next) { struct _ins_value * successor = lit->data; // don't add call edges if (successor->type == INS_SUC_CALL) continue; graph_add_edge(graph, ins->address, successor->address, successor); } } object_delete(map); return graph; }