#define __STDC_FORMAT_MACROS 1 #define NOMINMAX #ifdef _MSC_VER // get this out of the way... it's requried deep within unicorn #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include } #include "sim_unicorn.hpp" //#include "log.h" std::map global_cache; State::State(uc_engine *_uc, uint64_t cache_key, simos_t curr_os, bool symb_addrs, bool symb_cond, bool symb_syscalls): uc(_uc), simos(curr_os), handle_symbolic_addrs(symb_addrs), handle_symbolic_conditions(symb_cond), handle_symbolic_syscalls(symb_syscalls) { hooked = false; h_read = h_write = h_block = h_prot = 0; max_steps = cur_steps = 0; stopped = true; stop_details.stop_reason = STOP_NOSTART; ignore_next_block = false; ignore_next_selfmod = false; interrupt_handled = false; cgc_random_sysno = -1; cgc_receive_sysno = -1; cgc_transmit_sysno = -1; vex_guest = VexArch_INVALID; syscall_count = 0; uc_context_alloc(uc, &saved_regs); executed_pages_iterator = NULL; mem_updates_head = NULL; auto it = global_cache.find(cache_key); if (it == global_cache.end()) { page_cache = new PageCache(); global_cache[cache_key] = {page_cache}; } else { page_cache = it->second.page_cache; } arch = *((uc_arch*)uc); // unicorn hides all its internals... unicorn_mode = *((uc_mode*)((uc_arch*)uc + 1)); curr_block_details.reset(); symbolic_read_in_progress = false; trace_last_block_addr = 0; trace_last_block_tot_count = -1; trace_last_block_curr_count = -1; executed_blocks_count = -1; } /* * HOOK_MEM_WRITE is called before checking if the address is valid. so we might * see uninitialized pages. Using HOOK_MEM_PROT is too late for tracking taint. * so we don't have to use HOOK_MEM_PROT to track dirty pages. */ void State::hook() { if (hooked) { //LOG_D("already hooked"); return ; } uc_err err; err = uc_hook_add(uc, &h_read, UC_HOOK_MEM_READ, (void *)hook_mem_read, this, 1, 0); err = uc_hook_add(uc, &h_write, UC_HOOK_MEM_WRITE, (void *)hook_mem_write, this, 1, 0); err = uc_hook_add(uc, &h_block, UC_HOOK_BLOCK, (void *)hook_block, this, 1, 0); err = uc_hook_add(uc, &h_prot, UC_HOOK_MEM_PROT, (void *)hook_mem_prot, this, 1, 0); err = uc_hook_add(uc, &h_unmap, UC_HOOK_MEM_UNMAPPED, (void *)hook_mem_unmapped, this, 1, 0); err = uc_hook_add(uc, &h_intr, UC_HOOK_INTR, (void *)hook_intr, this, 1, 0); hooked = true; } void State::unhook() { if (!hooked) return ; uc_err err; err = uc_hook_del(uc, h_read); err = uc_hook_del(uc, h_write); err = uc_hook_del(uc, h_block); err = uc_hook_del(uc, h_prot); err = uc_hook_del(uc, h_unmap); err = uc_hook_del(uc, h_intr); hooked = false; h_read = h_write = h_block = h_prot = h_unmap = 0; } uc_err State::start(address_t pc, uint64_t step) { stopped = false; stop_details.stop_reason = STOP_NOSTART; max_steps = step; cur_steps = -1; unicorn_next_instr_addr = pc; executed_pages.clear(); // error if pc is 0 // TODO: why is this check here and not elsewhere if (pc == 0) { stop_details.stop_reason = STOP_ZEROPAGE; cur_steps = 0; return UC_ERR_MAP; } uc_err out = uc_emu_start(uc, unicorn_next_instr_addr, 0, 0, 0); if (stop_details.stop_reason != STOP_NOSTART) { rollback(); } if (out == UC_ERR_OK && stop_details.stop_reason == STOP_NOSTART && get_instruction_pointer() == 0) { // handle edge case where we stop because we reached our bogus stop address (0) stop_details.stop_reason = STOP_ZEROPAGE; } if (out == UC_ERR_INSN_INVALID) { stop_details.stop_reason = STOP_NODECODE; } // if we errored out right away, fix the step count to 0 if (cur_steps == -1) cur_steps = 0; return out; } void State::stop(stop_t reason, bool do_commit) { if (stopped) { // Do not stop if already stopped. Sometimes, python lands initiates a stop even though native interface has // already stopped leading to multiple issues. return; } stopped = true; stop_details.stop_reason = reason; stop_details.block_addr = curr_block_details.block_addr; stop_details.block_size = curr_block_details.block_size; if ((reason == STOP_SYSCALL) || do_commit) { commit(); } else if ((reason != STOP_NORMAL) && (reason != STOP_STOPPOINT)) { // Stop reason is not NORMAL, STOPPOINT or SYSCALL. Rollback. // EXECNONE, ZEROPAGE, NOSTART, ZERO_DIV, NODECODE and HLT are never passed to this function. rollback(); } uc_emu_stop(uc); // Prepare details of blocks with symbolic instructions to re-execute for returning to state plugin for (auto &block: blocks_with_symbolic_stmts) { if (block.symbolic_stmts.size() == 0) { continue; } sym_block_details_t sym_block; sym_block.reset(); sym_block.block_addr = block.block_addr; sym_block.block_size = block.block_size; sym_block.block_trace_ind = block.block_trace_ind; sym_block.has_symbolic_exit = block.has_symbolic_exit; std::set sym_stmts; std::unordered_set reg_values; for (auto &sym_stmt: block.symbolic_stmts) { auto sym_stmt_list = get_list_of_dep_stmts(sym_stmt); sym_stmts.insert(sym_stmt_list.begin(), sym_stmt_list.end()); sym_stmts.insert(sym_stmt); reg_values.insert(sym_stmt.reg_deps.begin(), sym_stmt.reg_deps.end()); } sym_block.register_values.insert(sym_block.register_values.end(), reg_values.begin(), reg_values.end()); for (auto &stmt: sym_stmts) { sym_vex_stmt_details_t sym_stmt; sym_stmt.stmt_idx = stmt.stmt_idx; sym_stmt.memory_values = stmt.memory_values; sym_stmt.memory_values_count = stmt.memory_values_count; sym_stmt.has_memory_dep = stmt.has_concrete_memory_dep || stmt.has_symbolic_memory_dep; sym_block.symbolic_stmts.emplace_back(sym_stmt); } block_details_to_return.emplace_back(sym_block); } } bool State::step(address_t current_address, int32_t size, bool check_stop_points) { bool result = false; if (track_bbls) { bbl_addrs.push_back(current_address); } if (track_stack) { stack_pointers.push_back(get_stack_pointer()); } executed_pages.insert(current_address & ~0xFFFULL); cur_address = current_address; cur_size = size; if (cur_steps >= max_steps) { stop(STOP_NORMAL); } else if (uc_procedures.count(current_address) != 0) { uc_procedures[current_address](this); result = true; commit(); } else if (check_stop_points) { // If size is zero, that means that the current basic block was too large for qemu // and it got split into multiple parts. unicorn will only call this hook for the // first part and not for the remaining ones, so it is impossible to find the // accurate size of the BB block here. // // See https://github.com/unicorn-engine/unicorn/issues/874 // // Until that is resolved, we use the maximum size of a Qemu basic block here. This means // that some stop points may not work, but there is no way to do better. uint32_t real_size = size == 0 ? MAX_BB_SIZE : size; // if there are any stop points in the current basic block, then there is no chance // for us to stop in the middle of a block. // since we do not support stopping in the middle of a block. auto stop_point = stop_points.lower_bound(current_address); if (stop_point != stop_points.end() && *stop_point < current_address + real_size) { stop(STOP_STOPPOINT); } else if ((trace_last_block_tot_count > 0) && (trace_last_block_addr >= current_address) && (trace_last_block_addr < current_address + real_size) && (trace_last_block_curr_count == trace_last_block_tot_count - 1)) { // Executing last block in trace. Stop. stop(STOP_STOPPOINT); } } if (!stopped) { executed_blocks_count++; } return result; } void State::commit() { // mark memory sync status // we might miss some dirty bits, this happens if hitting the memory // write before mapping /* THIS SHOULDN'T BE REQUIRED, we have the same logic to do this in the page activation code for (auto it = mem_writes.begin(); it != mem_writes.end(); it++) { if (it->clean == -1) { taint_t *bitmap = page_lookup(it->address); if (it->is_symbolic) { memset(&bitmap[it->address & 0xFFFULL], TAINT_SYMBOLIC, sizeof(taint_t) * it->size); } else { memset(&bitmap[it->address & 0xFFFULL], TAINT_DIRTY, sizeof(taint_t) * it->size); it->clean = (1 << it->size) - 1; //LOG_D("commit: lazy initialize mem_write [%#lx, %#lx]", it->address, it->address + it->size); } } } */ // clear memory rollback status mem_writes.clear(); cur_steps++; // Remove instructions whose effects are overwritten by subsequent instructions from the re-execute list for (auto &stmts_to_erase_entry: symbolic_stmts_to_erase) { auto block_it = blocks_with_symbolic_stmts.begin() + stmts_to_erase_entry.first; auto first_stmt_it = block_it->symbolic_stmts.begin(); for (auto &stmt_offset: stmts_to_erase_entry.second) { auto stmt_to_erase = first_stmt_it + stmt_offset; for (auto &sym_mem_dep: stmt_to_erase->symbolic_mem_deps) { auto elem = symbolic_mem_deps.find(sym_mem_dep); elem->second--; if (elem->second == 0) { // No more statements to be re-executed require this symbolic memory dependency. symbolic_mem_deps.erase(elem); } } if (stmt_to_erase->has_memory_write && (stmt_to_erase->mem_write_addr != -1)) { // Statement has a symbolic write that need not be tracked since it won't be re-executed auto write_start_addr = stmt_to_erase->mem_write_addr; auto write_end_addr = stmt_to_erase->mem_write_addr + stmt_to_erase->mem_write_size; for (auto byte_addr = write_start_addr; byte_addr < write_end_addr; byte_addr++) { auto elem = symbolic_mem_writes.find(byte_addr); elem->second--; if (elem->second == 0) { // No more statements to be re-executed write symbolic to this address. symbolic_mem_writes.erase(elem); } } } block_it->symbolic_stmts.erase(stmt_to_erase); } } // Save details of symbolic instructions in current block if (curr_block_details.symbolic_stmts.size() > 0) { for (auto &symbolic_stmt: curr_block_details.symbolic_stmts) { compute_slice_of_stmt(symbolic_stmt); // Update info of symbolic memory dependencies of this statement or any of its dependencies if (symbolic_stmt.has_symbolic_memory_dep) { for (auto &mem_value: block_mem_reads_map.at(symbolic_stmt.stmt_idx).memory_values) { if (mem_value.is_value_symbolic) { symbolic_stmt.symbolic_mem_deps.emplace(mem_value.address); auto elem = symbolic_mem_deps.find(mem_value.address); if (elem == symbolic_mem_deps.end()) { symbolic_mem_deps.emplace(mem_value.address, 1); } else { elem->second++; } } } } for (auto &dep_stmt: symbolic_stmt.stmt_deps) { if (dep_stmt.has_symbolic_memory_dep) { for (auto &mem_value: block_mem_reads_map.at(dep_stmt.stmt_idx).memory_values) { if (mem_value.is_value_symbolic) { symbolic_stmt.symbolic_mem_deps.emplace(mem_value.address); auto elem = symbolic_mem_deps.find(mem_value.address); if (elem == symbolic_mem_deps.end()) { symbolic_mem_deps.emplace(mem_value.address, 1); } else { elem->second++; } } } } } // Save all concrete memory dependencies of the block save_concrete_memory_deps(symbolic_stmt); } blocks_with_symbolic_stmts.emplace_back(curr_block_details); } if (curr_block_details.block_addr == trace_last_block_addr) { trace_last_block_curr_count += 1; } // Update details of concrete writes to re-execute for (auto &write_addr: block_concrete_writes_to_reexecute) { uint8_t concrete_value; uc_mem_read(uc, write_addr, &concrete_value, 1); auto reexec_write_entry = concrete_writes_to_reexecute.find(write_addr); if (reexec_write_entry == concrete_writes_to_reexecute.end()) { // This is a new concrete write that needs to be re-executed concrete_writes_to_reexecute.emplace(write_addr, concrete_value); } else { // A previous concrete write to same address is marked for re-execution. Update its value. reexec_write_entry->second = concrete_value; } } // Update details of symbolic writes in the block for (auto &write_addr: block_symbolic_mem_writes) { auto elem = symbolic_mem_writes.find(write_addr); if (elem == symbolic_mem_writes.end()) { symbolic_mem_writes.emplace(write_addr, 1); } else { elem->second++; } } // Sync all block level taint statuses reads with state's taint statuses and block level // symbolic instruction list with state's symbolic instruction list for (auto ®_offset: block_symbolic_registers) { symbolic_registers.emplace(reg_offset); } for (auto ®_offset: block_concrete_registers) { symbolic_registers.erase(reg_offset); } // save registers uc_context_save(uc, saved_regs); // Clear all block level taint status trackers and symbolic instruction list block_symbolic_registers.clear(); block_concrete_registers.clear(); block_stmt_concrete_regs.clear(); block_symbolic_mem_deps.clear(); block_symbolic_mem_writes.clear(); block_concrete_writes_to_reexecute.clear(); curr_block_details.reset(); block_mem_reads_data.clear(); block_mem_reads_map.clear(); block_mem_read_addr_details.clear(); block_mem_writes_taint_data.clear(); symbolic_stmts_to_erase.clear(); taint_engine_next_stmt_idx = -1; taint_engine_stop_mem_read_instruction = 0; taint_engine_stop_mem_read_size = 0; return; } void State::rollback() { // roll back memory changes for (auto rit = mem_writes.rbegin(); rit != mem_writes.rend(); rit++) { uc_err err = uc_mem_write(uc, rit->address, rit->value.data(), rit->size); if (err) { //LOG_I("rollback: %s", uc_strerror(err)); break; } auto page = page_lookup(rit->address); uint64_t start = rit->address & 0xFFF; int size = rit->size; for (auto i = 0; i < size; i++) { taint_t previous = rit->previous_taint[i]; bitmap_set(page.symbolic, start + i, previous == TAINT_SYMBOLIC); if (page.dirty != NULL) { bitmap_set(page.dirty, start + i, previous == TAINT_DIRTY); } } } mem_writes.clear(); // restore registers uc_context_restore(uc, saved_regs); if (track_bbls) bbl_addrs.pop_back(); if (track_stack) stack_pointers.pop_back(); } /* * return the taint bitmaps only if the page is remapped for writing, * or initialized with symbolic variable, otherwise return NULLs. */ page_taint_t State::page_lookup(address_t address) const { address &= ~0xFFFULL; auto it = active_pages.find(address); if (it == active_pages.end()) { page_taint_t missing = {NULL, NULL, NULL}; return missing; } return it->second; } void State::page_activate(address_t address, uint8_t *taint, uint8_t *data) { address &= ~0xFFFULL; auto it = active_pages.find(address); if (it == active_pages.end()) { page_taint_t page; page.data = data; if (data == NULL) { // The page is copied into unicorn: we need our own copy of the symbolic bitmap, plus a dirty bitmap to // remember which bytes have to be synced back to angr. page.symbolic = new PageBitmap; memcpy(page.symbolic, taint, sizeof(PageBitmap)); page.dirty = new uint8_t[ANGR_PAGE_BITMAP_SIZE](); } else { // The page is direct-mapped: angr's symbolic bitmap is ours to update in place, and concrete writes go // straight into angr's backing store, so nothing ever needs syncing. page.symbolic = taint; page.dirty = NULL; } active_pages.insert(std::pair(address, page)); } else { // TODO: un-hardcode this address, or at least do this warning from python land if (address == 0x4000) { printf("[sim_unicorn] You've mapped something at 0x4000! " "Please don't do that, I put my GDT there!\n"); } else { printf("[sim_unicorn] Something very bad is happening; please investigate. " "Trying to activate the page at %#" PRIx64 " but it's already activated.\n", address); // to the person who sees this error: // you're gonna need to spend some time looking into it. // I'm not 100% sure that this is necessarily a bug condition. } } /* SHOULD NOT BE NECESSARY for (auto a = mem_writes.begin(); a != mem_writes.end(); a++) if (a->clean == -1 && (a->address & ~0xFFFULL) == address) { // This mapping was prompted by this write. // initialize this memory access immediately so that the // record is valid. //printf("page_activate: lazy initialize mem_write [%#lx, %#lx]\n", a->address, a->address + a->size); if (data == NULL) { memset(&bitmap[a->address & 0xFFFULL], TAINT_DIRTY, sizeof(taint_t) * a->size); a->clean = (1ULL << a->size) - 1; } else { a->clean = 0; for (int i = 0; i < a->size; i++) { if (bitmap[(a->address & 0xFFFULL) + i] == TAINT_SYMBOLIC) { a->clean |= 1 << i; } } memset(&bitmap[a->address & 0xFFFULL], TAINT_CLEAN, sizeof(taint_t) * a->size); memcpy(a->value, &data[a->address & 0xFFFULL], a->size); } } */ } mem_update_t *State::sync() { for (auto it = active_pages.begin(); it != active_pages.end(); it++) { uint8_t *dirty = it->second.dirty; if (dirty == NULL) { // nothing to sync, direct mapped :) continue; } //LOG_D("found active page %#lx (%p)", it->first, dirty); for (uint64_t i = 0; i < ANGR_PAGE_SIZE; i++) if (bitmap_get(dirty, i)) { uint64_t j = i; while (j < ANGR_PAGE_SIZE && bitmap_get(dirty, j)) j++; char buf[ANGR_PAGE_SIZE]; uc_mem_read(uc, it->first + i, buf, j - i); //LOG_D("sync [%#lx, %#lx] = %#lx", it->first + i, it->first + j, *(uint64_t *)buf); mem_update_t *range = new mem_update_t; range->address = it->first + i; range->length = j - i; range->next = mem_updates_head; mem_updates_head = range; i = j; } } return mem_updates_head; } void State::set_last_block_details(address_t block_addr, int64_t curr_count, int64_t tot_count) { trace_last_block_addr = block_addr; trace_last_block_curr_count = curr_count; trace_last_block_tot_count = tot_count; return; } void State::set_stops(uint64_t count, address_t *stops) { stop_points.clear(); for (auto i = 0; i < count; i++) { stop_points.insert(stops[i]); } } std::pair State::cache_page(address_t address, size_t size, char* bytes, uint64_t permissions) { assert(address % 0x1000 == 0); assert(size % 0x1000 == 0); for (auto offset = 0; offset < size; offset += 0x1000) { auto page = page_cache->find(address+offset); if (page != page_cache->end()) { fprintf(stderr, "[%#" PRIx64 ", %#" PRIx64 "](%#zx) already in cache.\n", address+offset, address+offset + 0x1000, 0x1000lu); assert(page->second.size == 0x1000); assert(memcmp(page->second.bytes, bytes + offset, 0x1000) == 0); continue; } uint8_t *copy = (uint8_t *)malloc(0x1000); CachedPage cached_page = { 0x1000, copy, permissions }; // address should be aligned to 0x1000 memcpy(copy, &bytes[offset], 0x1000); page_cache->insert(std::pair(address+offset, cached_page)); } return std::make_pair(address, size); } void State::wipe_page_from_cache(address_t address) { auto page = page_cache->find(address); if (page != page_cache->end()) { //printf("Internal: unmapping %#llx size %#x, result %#x", page->first, page->second.size, uc_mem_unmap(uc, page->first, page->second.size)); uc_err err = uc_mem_unmap(uc, page->first, page->second.size); //if (err) { // fprintf(stderr, "wipe_page_from_cache [%#lx, %#lx]: %s\n", page->first, page->first + page->second.size, uc_strerror(err)); //} free(page->second.bytes); // might explode page_cache->erase(page); } else { //printf("Uh oh! Couldn't find page at %#llx\n", address); } } void State::uncache_pages_touching_region(address_t address, uint64_t length) { address &= ~(0x1000-1); for (auto offset = 0; offset < length; offset += 0x1000) { wipe_page_from_cache(address + offset); } } void State::clear_page_cache() { while (!page_cache->empty()) { wipe_page_from_cache(page_cache->begin()->first); } } bool State::map_cache(address_t address, size_t size) { assert(address % 0x1000 == 0); assert(size % 0x1000 == 0); bool success = true; for (auto offset = 0; offset < size; offset += 0x1000) { auto page = page_cache->find(address+offset); if (page == page_cache->end()) { success = false; continue; } auto cached_page = page->second; size_t page_size = cached_page.size; uint8_t *bytes = cached_page.bytes; uint64_t permissions = cached_page.perms; assert(page_size == 0x1000); //LOG_D("hit cache [%#lx, %#lx]", address, address + size); uc_err err = uc_mem_map_ptr(uc, page->first, page_size, permissions, bytes); if (err) { fprintf(stderr, "map_cache [%#lx, %#lx]: %s\n", address, address + size, uc_strerror(err)); success = false; continue; } } return success; } bool State::in_cache(address_t address) const { return page_cache->find(address) != page_cache->end(); } // Finds tainted data in the provided range and returns the address. // Returns -1 if no tainted data is present. int64_t State::find_tainted(address_t address, int size) { uint8_t *bitmap = page_lookup(address).symbolic; int start = address & 0xFFF; int end = (address + size - 1) & 0xFFF; if (end >= start) { if (bitmap) { for (auto i = start; i <= end; i++) { if (bitmap_get(bitmap, i)) { return (address & ~0xFFF) + i; } } } } else { // cross page boundary if (bitmap) { for (auto i = start; i <= 0xFFF; i++) { if (bitmap_get(bitmap, i)) { return (address & ~0xFFF) + i; } } } bitmap = page_lookup(address + size - 1).symbolic; if (bitmap) { for (auto i = 0; i <= end; i++) { if (bitmap_get(bitmap, i)) { return ((address + size - 1) & ~0xFFF) + i; } } } } return -1; } void State::handle_write(address_t address, int size, bool is_interrupt = false, bool interrupt_value_symbolic = false) { // If the write spans a page, chop it up if ((address & 0xfff) + size > 0x1000) { int chopsize = 0x1000 - (address & 0xfff); handle_write(address, chopsize, is_interrupt, interrupt_value_symbolic); if (stopped) { return; } handle_write(address + chopsize, size - chopsize, is_interrupt, interrupt_value_symbolic); return; } // From here, we are definitely only dealing with one page mem_write_t record; record.address = address; record.size = size; record.value.resize(size); uc_err err = uc_mem_read(uc, address, record.value.data(), size); if (err == UC_ERR_READ_UNMAPPED) { if (py_mem_callback(uc, UC_MEM_WRITE_UNMAPPED, address, size, 0, (void*)0)) { err = UC_ERR_OK; } } if (err) { stop(STOP_ERROR); return; } auto page = page_lookup(address); uint8_t *bitmap = page.symbolic; uint8_t *dirty = page.dirty; uint8_t *data = page.data; int start = address & 0xFFF; int end = (address + size - 1) & 0xFFF; short clean; address_t curr_instr_addr = 0; bool is_dst_symbolic; if (!bitmap) { // We should never have a missing bitmap because we explicitly called the callback! printf("This should never happen, right? %#" PRIx64 "\n", address); abort(); } clean = 0; if (is_interrupt) { is_dst_symbolic = interrupt_value_symbolic; } else if (is_symbolic_tracking_disabled() || curr_block_details.vex_lift_failed) { // If symbolic tracking is disabled, all writes are concrete // is_interrupt flag is a workaround for CGC transmit syscall, which never passes symbolic data // If VEX lift failed, then write is definitely concrete since execution continues only // if no symbolic data is present is_dst_symbolic = false; } else if (size == 0) { // unicorn was unable to determine size of the memory write. Treat as failure since we cannot determine final // taint status stop(STOP_UNKNOWN_MEMORY_WRITE_SIZE); return; } else if ((block_mem_writes_taint_data.size() == 0) && (symbolic_registers.size() == 0) && (block_symbolic_registers.size() == 0) && (block_symbolic_temps.size() == 0)) { // No write taint info saved and there are no symbolic registers/VEX temps => write is concrete is_dst_symbolic = false; } else { // Determine destination's taint using block's memory writes taint info is_dst_symbolic = false; uint32_t size_of_writes_processed = 0; while (size_of_writes_processed != size) { if (block_mem_writes_taint_data.size() == 0) { // All writes have been processed but there should be more. Likely a bug. printf("All memory writes have been processed but more expected. This should not happen!\n"); abort(); } auto &next_mem_write = block_mem_writes_taint_data.front(); is_dst_symbolic |= next_mem_write.is_symbolic; if (curr_instr_addr == 0) { curr_instr_addr = next_mem_write.instr_addr; } else if (curr_instr_addr != next_mem_write.instr_addr) { printf("Memory writes from two instructions being processed as part of one instruction. This should not happen!\n"); abort(); } if (size_of_writes_processed + next_mem_write.size > size) { // Including current write entry exceeds size of current write reported by unicorn. Update size of write // entry instead of erasing it completely block_mem_writes_taint_data[0].size = size_of_writes_processed + next_mem_write.size - size; break; } else { // Size of all writes so far does not exceed current write. Erase current write entry. size_of_writes_processed += next_mem_write.size; block_mem_writes_taint_data.erase(block_mem_writes_taint_data.begin()); } } } if (is_dst_symbolic) { // Track details of symbolic memory write. Remove any concrete writes to same location marked for re-execution. for (auto byte_addr = address; byte_addr < address + size; byte_addr++) { block_symbolic_mem_writes.emplace(byte_addr); auto prev_write_reexec_entry = concrete_writes_to_reexecute.find(byte_addr); if (prev_write_reexec_entry != concrete_writes_to_reexecute.end()) { concrete_writes_to_reexecute.erase(prev_write_reexec_entry); } auto curr_block_write_reexec_entry = block_concrete_writes_to_reexecute.find(byte_addr); if (curr_block_write_reexec_entry != block_concrete_writes_to_reexecute.end()) { block_concrete_writes_to_reexecute.erase(curr_block_write_reexec_entry); } } if (!is_interrupt) { // Save the details of memory location written to in the statement details for (auto &symbolic_stmt: curr_block_details.symbolic_stmts) { if ((symbolic_stmt.instr_addr == curr_instr_addr) && symbolic_stmt.has_memory_write) { symbolic_stmt.mem_write_addr = address; symbolic_stmt.mem_write_size = size; break; } } } } if ((find_tainted(address, size) != -1) & (!is_dst_symbolic)) { // We are writing to a memory location that is currently symbolic. If the destination if a memory dependency // of some instruction to be re-executed, we need to re-execute that instruction before continuing. auto curr_write_start_addr = address; auto curr_write_end_addr = address + size - 1; bool erase_previous_mem_write = true; for (auto &symbolic_mem_dep: symbolic_mem_deps) { if ((curr_write_start_addr <= symbolic_mem_dep.first) && (symbolic_mem_dep.first <= curr_write_end_addr)) { // The concrete write write overwrites a symbolic value that is needed during re-execution. Track // details of concrete write for later re-execution erase_previous_mem_write = false; } } for (auto &symbolic_mem_dep: block_symbolic_mem_deps) { if ((curr_write_start_addr <= symbolic_mem_dep) && (symbolic_mem_dep <= curr_write_end_addr)) { // The concrete write write overwrites a symbolic value that is needed during re-execution. Track // details of concrete write for later re-execution erase_previous_mem_write = false; } } // Check if there is a symbolic write to same location. If no, then there is no write to erase. for (auto byte_addr = curr_write_start_addr; byte_addr <= curr_write_end_addr; byte_addr++) { if ((symbolic_mem_writes.count(byte_addr) == 0) && (block_symbolic_mem_writes.count(byte_addr) == 0)) { erase_previous_mem_write = false; } } // The destination is not a memory dependency of some instruction to be re-executed. We now check if any // instructions to be re-executed write to this same location. If there is one, they need not be re-executed // since this concrete write nullifies their effects. if (erase_previous_mem_write) { auto first_block_it = blocks_with_symbolic_stmts.begin(); for (auto block_it = first_block_it; block_it != blocks_with_symbolic_stmts.end(); block_it++) { std::unordered_set stmts_to_erase; auto first_stmt_it = block_it->symbolic_stmts.begin(); for (auto sym_stmt_it = first_stmt_it; sym_stmt_it != block_it->symbolic_stmts.end(); sym_stmt_it++) { int64_t symbolic_write_start_addr = sym_stmt_it->mem_write_addr; if (symbolic_write_start_addr == -1) { // Instruction does not write a symbolic write to memory. No need to check this. continue; } int64_t symbolic_write_end_addr = sym_stmt_it->mem_write_addr + sym_stmt_it->mem_write_size; if ((curr_write_start_addr <= symbolic_write_start_addr) && (symbolic_write_end_addr <= curr_write_end_addr)) { // Current write fully overwrites the previous written symbolic value and so the symbolic write // instruction need not be re-executed // TODO: How to handle partial overwrite? stmts_to_erase.emplace(sym_stmt_it - first_stmt_it); } } if (stmts_to_erase.size() > 0) { symbolic_stmts_to_erase.emplace(block_it - first_block_it, stmts_to_erase); } } } } if (!is_dst_symbolic) { for (auto byte_addr = address; byte_addr < address + size; byte_addr++) { if (concrete_writes_to_reexecute.find(byte_addr) != concrete_writes_to_reexecute.end()) { // We are writing a concrete value to the same address as some concrete write to be reexecuted. Record // address to later update concrete value to write. block_concrete_writes_to_reexecute.emplace(byte_addr); } else if (syscall_concrete_writes.find(byte_addr) != syscall_concrete_writes.end()) { // Mark write for re-execution since value will be overwritten by a previously executed syscall that // will be re-executed. block_concrete_writes_to_reexecute.emplace(byte_addr); } else if ((symbolic_mem_writes.count(byte_addr) > 0) || (block_symbolic_mem_writes.count(byte_addr) > 0)) { // A previous symbolic write to same location will be re-executed and so re-execute concrete write. block_concrete_writes_to_reexecute.emplace(byte_addr); } } } if (data == NULL) { for (auto i = start; i <= end; i++) { // a byte is never both symbolic and dirty, so the two bitmaps recover the previous taint between them taint_t previous = TAINT_NONE; if (bitmap_get(bitmap, i)) { previous = TAINT_SYMBOLIC; } else if (bitmap_get(dirty, i)) { previous = TAINT_DIRTY; } record.previous_taint.push_back(previous); // Don't mark a symbolic write as TAINT_DIRTY since we don't want to sync it back to angr // Also, no need to set clean: rollback will set it to TAINT_NONE which // is fine for symbolic bytes and rollback is called when exiting unicorn // due to an error encountered bitmap_set(bitmap, i, is_dst_symbolic); bitmap_set(dirty, i, !is_dst_symbolic); } } else { for (auto i = start; i <= end; i++) { // the page is direct-mapped, so concrete writes need no syncing and there is no dirty bitmap to update record.previous_taint.push_back(bitmap_get(bitmap, i) ? TAINT_SYMBOLIC : TAINT_NONE); bitmap_set(bitmap, i, is_dst_symbolic); } } mem_writes.push_back(record); for (int i = 0; i < size; i++) { auto entry = block_mem_read_addr_details.find(address + i); if (entry != block_mem_read_addr_details.end()) { // Some previous VEX statements read values from this address that is being written to. Save current value. for (auto &vex_stmt_id: entry->second) { auto &mem_read_result = block_mem_reads_map.at(vex_stmt_id); for (auto &mem_value: mem_read_result.memory_values) { if (!mem_value.is_value_set && (mem_value.address == address + i)) { mem_value.value = record.value.at(i); mem_value.is_value_set = true; } } } } } } void State::compute_slice_of_stmt(vex_stmt_details_t &stmt) { // Compute block slice of instruction needed to setup concrete registers needed by it and also save values of // registers not changed from start of the block std::vector stmts_to_process; bool all_dep_regs_concrete = false; auto &block_taint_entry = block_taint_cache.at(curr_block_details.block_addr); auto &stmt_taint_entry = block_taint_entry.block_stmts_taint_data.at(stmt.stmt_idx); auto stmt_concrete_regs_it = block_stmt_concrete_regs.find(stmt.stmt_idx); if (stmt_concrete_regs_it == block_stmt_concrete_regs.end()) { // No entry was added for this statement because no symbolic values existed when the statement was // processed all_dep_regs_concrete = true; } for (auto &source: stmt_taint_entry.sources) { if (source.entity_type == TAINT_ENTITY_TMP) { stmts_to_process.emplace_back(source.stmt_idx); } else if (source.entity_type == TAINT_ENTITY_REG) { if(source.stmt_idx != -1) { // Source was modified by some previous statement. Compute its slice. stmts_to_process.emplace_back(source.stmt_idx); } else if (all_dep_regs_concrete || (stmt_concrete_regs_it->second.count(source.reg_offset) > 0)) { // Source is a register dependency that is not modified from start of block till this statement. Save // its value. register_value_t dep_reg_val; dep_reg_val.offset = source.reg_offset; dep_reg_val.size = source.value_size; get_register_value(dep_reg_val.offset, dep_reg_val.value, true); stmt.reg_deps.insert(dep_reg_val); } } else if (source.entity_type == TAINT_ENTITY_MEM) { // Compute slice to set up the read address correctly for (auto &mem_ref_entity: source.mem_ref_entity_list) { // TODO: Are addresses for memory reads always stored in VEX temps? assert(mem_ref_entity.entity_type == TAINT_ENTITY_TMP); stmts_to_process.emplace_back(mem_ref_entity.stmt_idx); } } } for (auto &entity: stmt_taint_entry.ite_cond_entity_list) { // Compute slice for VEX temps used in condition of ITE expression // TODO: Do ITE conditions contain only VEX temps? assert(entity.entity_type == TAINT_ENTITY_TMP); stmts_to_process.emplace_back(entity.stmt_idx); } // If statement performs a memory store, compute slice to set up the write address correctly. if (stmt_taint_entry.sink.entity_type == TAINT_ENTITY_MEM) { for (auto &source: stmt_taint_entry.sink.mem_ref_entity_list) { // TODO: Are addresses for memory writes always stored in VEX temps? assert(source.entity_type == TAINT_ENTITY_TMP); stmts_to_process.emplace_back(source.stmt_idx); } } for (auto &dep_stmt_idx: stmts_to_process) { auto &dep_stmt_taint_entry = block_taint_entry.block_stmts_taint_data.at(dep_stmt_idx); vex_stmt_details_t dep_stmt_details = compute_vex_stmt_details(dep_stmt_taint_entry); compute_slice_of_stmt(dep_stmt_details); stmt.reg_deps.insert(dep_stmt_details.reg_deps.begin(), dep_stmt_details.reg_deps.end()); stmt.stmt_deps.insert(dep_stmt_details.stmt_deps.begin(), dep_stmt_details.stmt_deps.end()); dep_stmt_details.reg_deps.clear(); dep_stmt_details.stmt_deps.clear(); stmt.stmt_deps.insert(dep_stmt_details); } return; } void State::process_vex_block(IRSB *vex_block, address_t address) { block_taint_entry_t block_taint_entry; vex_stmt_taint_entry_t stmt_taint_entry; address_t curr_instr_addr; std::unordered_map last_entity_setter; std::unordered_set modified_regs; block_taint_entry.has_unsupported_stmt_or_expr_type = false; for (auto stmt_idx = 0; stmt_idx < vex_block->stmts_used; stmt_idx++) { auto stmt = vex_block->stmts[stmt_idx]; switch (stmt->tag) { case Ist_Put: { stmt_taint_entry.sink.entity_type = TAINT_ENTITY_REG; stmt_taint_entry.sink.instr_addr = curr_instr_addr; stmt_taint_entry.sink.stmt_idx = stmt_idx; stmt_taint_entry.sink.reg_offset = stmt->Ist.Put.offset; auto entry = last_entity_setter.find(stmt_taint_entry.sink); if (entry != last_entity_setter.end()) { entry->second = stmt_idx; } else { last_entity_setter.emplace(stmt_taint_entry.sink, stmt_idx); } auto result = process_vex_expr(stmt->Ist.Put.data, vex_block->tyenv, curr_instr_addr, stmt_idx, last_entity_setter, false); if (result.has_unsupported_expr) { block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = result.unsupported_expr_stop_reason; break; } stmt_taint_entry.sink.value_size = result.value_size; stmt_taint_entry.floating_point_op_skip = result.has_floating_point_op_to_skip; if (!stmt_taint_entry.floating_point_op_skip) { // Flatten list of taint sources and also save them as dependencies of instruction // TODO: Should we not save dependencies if sink is an artificial register? stmt_taint_entry.sources.insert(result.taint_sources.begin(), result.taint_sources.end()); stmt_taint_entry.ite_cond_entity_list.insert(result.ite_cond_entities.begin(), result.ite_cond_entities.end()); if (result.mem_read_size != 0) { stmt_taint_entry.mem_read_size += result.mem_read_size; stmt_taint_entry.has_memory_read = true; } } block_taint_entry.block_stmts_taint_data.emplace(stmt_idx, stmt_taint_entry); if (vex_cc_regs.find(stmt_taint_entry.sink.reg_offset) != vex_cc_regs.end()) { block_taint_entry.vex_cc_setter_stmts.emplace(stmt_idx); } stmt_taint_entry.reset(); break; } case Ist_WrTmp: { stmt_taint_entry.sink.entity_type = TAINT_ENTITY_TMP; stmt_taint_entry.sink.instr_addr = curr_instr_addr; stmt_taint_entry.sink.stmt_idx = stmt_idx; stmt_taint_entry.sink.tmp_id = stmt->Ist.WrTmp.tmp; auto entry = last_entity_setter.find(stmt_taint_entry.sink); if (entry != last_entity_setter.end()) { entry->second = stmt_idx; } else { last_entity_setter.emplace(stmt_taint_entry.sink, stmt_idx); } auto sink_type = vex_block->tyenv->types[stmt_taint_entry.sink.tmp_id]; if (sink_type == Ity_I1) { stmt_taint_entry.sink.value_size = 0; } else { stmt_taint_entry.sink.value_size = sizeofIRType(sink_type); } auto result = process_vex_expr(stmt->Ist.WrTmp.data, vex_block->tyenv, curr_instr_addr, stmt_idx, last_entity_setter, false); if (result.has_unsupported_expr) { // Only WrTmp statements will have GetI expressions on RHS. If we're concretizing all FP ops, mark temp as concrete if ((result.unsupported_expr_stop_reason == STOP_UNSUPPORTED_EXPR_GETI) && (fp_ops_to_avoid.size() > 0)) { stmt_taint_entry.floating_point_op_skip = true; stmt_taint_entry.sources.insert(result.taint_sources.begin(), result.taint_sources.end()); block_taint_entry.block_stmts_taint_data.emplace(stmt_idx, stmt_taint_entry); stmt_taint_entry.reset(); break; } block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = result.unsupported_expr_stop_reason; break; } stmt_taint_entry.floating_point_op_skip = result.has_floating_point_op_to_skip; if (!stmt_taint_entry.floating_point_op_skip) { stmt_taint_entry.sources.insert(result.taint_sources.begin(), result.taint_sources.end()); stmt_taint_entry.ite_cond_entity_list.insert(result.ite_cond_entities.begin(), result.ite_cond_entities.end()); if (result.mem_read_size != 0) { stmt_taint_entry.mem_read_size += result.mem_read_size; stmt_taint_entry.has_memory_read = true; } } block_taint_entry.block_stmts_taint_data.emplace(stmt_idx, stmt_taint_entry); stmt_taint_entry.reset(); break; } case Ist_Store: { stmt_taint_entry.sink.entity_type = TAINT_ENTITY_MEM; stmt_taint_entry.sink.instr_addr = curr_instr_addr; stmt_taint_entry.sink.stmt_idx = stmt_idx; auto entry = last_entity_setter.find(stmt_taint_entry.sink); if (entry != last_entity_setter.end()) { entry->second = stmt_idx; } else { last_entity_setter.emplace(stmt_taint_entry.sink, stmt_idx); } auto result = process_vex_expr(stmt->Ist.Store.addr, vex_block->tyenv, curr_instr_addr, stmt_idx, last_entity_setter, false); if (result.has_unsupported_expr) { block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = result.unsupported_expr_stop_reason; break; } // TODO: What if memory addresses have ITE expressions in them? stmt_taint_entry.sink.mem_ref_entity_list.insert(stmt_taint_entry.sink.mem_ref_entity_list.end(), result.taint_sources.begin(), result.taint_sources.end()); stmt_taint_entry.mem_read_size += result.mem_read_size; stmt_taint_entry.has_memory_read |= (result.mem_read_size != 0); result = process_vex_expr(stmt->Ist.Store.data, vex_block->tyenv, curr_instr_addr, stmt_idx, last_entity_setter, false); if (result.has_unsupported_expr) { block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = result.unsupported_expr_stop_reason; break; } stmt_taint_entry.sink.value_size = result.value_size; stmt_taint_entry.mem_write_size += result.value_size; stmt_taint_entry.floating_point_op_skip = result.has_floating_point_op_to_skip; if (!stmt_taint_entry.floating_point_op_skip) { stmt_taint_entry.sources.insert(result.taint_sources.begin(), result.taint_sources.end()); stmt_taint_entry.ite_cond_entity_list.insert(result.ite_cond_entities.begin(), result.ite_cond_entities.end()); if (result.mem_read_size != 0) { stmt_taint_entry.mem_read_size += result.mem_read_size; stmt_taint_entry.has_memory_read = true; } } block_taint_entry.block_stmts_taint_data.emplace(stmt_idx, stmt_taint_entry); stmt_taint_entry.reset(); break; } case Ist_Exit: { stmt_taint_entry.sink.entity_type = TAINT_ENTITY_NONE; stmt_taint_entry.is_exit = true; stmt_taint_entry.sink.instr_addr = curr_instr_addr; stmt_taint_entry.sink.stmt_idx = stmt_idx; auto result = process_vex_expr(stmt->Ist.Exit.guard, vex_block->tyenv, curr_instr_addr, stmt_idx, last_entity_setter, true); if (result.has_unsupported_expr) { block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = result.unsupported_expr_stop_reason; break; } stmt_taint_entry.floating_point_op_skip = result.has_floating_point_op_to_skip; if (!stmt_taint_entry.floating_point_op_skip) { stmt_taint_entry.sources.insert(result.taint_sources.begin(), result.taint_sources.end()); stmt_taint_entry.ite_cond_entity_list.insert(result.ite_cond_entities.begin(), result.ite_cond_entities.end()); if (result.mem_read_size != 0) { stmt_taint_entry.mem_read_size += result.mem_read_size; stmt_taint_entry.has_memory_read = true; } } block_taint_entry.block_stmts_taint_data.emplace(stmt_idx, stmt_taint_entry); block_taint_entry.has_exit_stmt = true; stmt_taint_entry.reset(); break; } case Ist_IMark: { curr_instr_addr = stmt->Ist.IMark.addr; break; } case Ist_PutI: { if (fp_ops_to_avoid.size() > 0) { // All FP ops are being concretized. Mark destination as concrete instead of stopping. stmt_taint_entry.sink.entity_type = TAINT_ENTITY_REG; stmt_taint_entry.sink.instr_addr = curr_instr_addr; stmt_taint_entry.sink.stmt_idx = stmt_idx; stmt_taint_entry.sink.reg_offset = fp_reg_vex_data.first; stmt_taint_entry.sink.value_size = fp_reg_vex_data.second; break; } // PutI statements cannot be handled in VEX since exact register's offset is computed at runtime. block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = STOP_UNSUPPORTED_STMT_PUTI; break; } case Ist_StoreG: { // TODO block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = STOP_UNSUPPORTED_STMT_STOREG; break; } case Ist_LoadG: { // TODO block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = STOP_UNSUPPORTED_STMT_LOADG; break; } case Ist_CAS: { // TODO block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = STOP_UNSUPPORTED_STMT_CAS; break; } case Ist_LLSC: { // TODO block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = STOP_UNSUPPORTED_STMT_LLSC; break; } case Ist_Dirty: { // TODO block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = STOP_UNSUPPORTED_STMT_DIRTY; if (strstr(stmt->Ist.Dirty.details->cee->name, "CPUID")) { block_taint_entry.has_cpuid_instr = true; } break; } case Ist_MBE: case Ist_NoOp: case Ist_AbiHint: break; default: { fprintf(stderr, "[sim_unicorn] Unsupported statement type encountered: "); fprintf(stderr, "Block: 0x%zx, statement index: %d, statement type: %u\n", address, stmt_idx, stmt->tag); block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = STOP_UNSUPPORTED_STMT_UNKNOWN; break; } } } // Process block default exit target auto block_next_taint_sources = process_vex_expr(vex_block->next, vex_block->tyenv, curr_instr_addr, -1, last_entity_setter, false); if (block_next_taint_sources.has_unsupported_expr) { block_taint_entry.has_unsupported_stmt_or_expr_type = true; block_taint_entry.unsupported_stmt_stop_reason = block_next_taint_sources.unsupported_expr_stop_reason; } else { block_taint_entry.block_next_entities.insert(block_next_taint_sources.taint_sources.begin(), block_next_taint_sources.taint_sources.end()); } block_taint_cache.emplace(address, block_taint_entry); return; } std::set State::get_list_of_dep_stmts(const vex_stmt_details_t &stmt) const { std::set stmts; for (auto &dep_stmts: stmt.stmt_deps) { auto result = get_list_of_dep_stmts(dep_stmts); stmts.insert(result.begin(), result.end()); stmts.insert(dep_stmts); } return stmts; } void State::get_register_value(uint64_t vex_reg_offset, uint8_t *out_reg_value, bool from_saved_context) const { // Check if VEX register is actually a CPU flag auto cpu_flags_entry = cpu_flags.find(vex_reg_offset); if (cpu_flags_entry != cpu_flags.end()) { uint64_t reg_value; if (from_saved_context) { uc_context_reg_read(saved_regs, cpu_flags_entry->second.first, ®_value); } else { uc_reg_read(uc, cpu_flags_entry->second.first, ®_value); } reg_value &= cpu_flags_entry->second.second; if (reg_value == 0) { memset(out_reg_value, 0, MAX_REGISTER_BYTE_SIZE); } else { // The flag is not 0 so we shift right until first non-zero bit is in LSB so that value of flag register // will be set correctly when re-executing for (int i = 1; i < MAX_REGISTER_BYTE_SIZE && ((reg_value & 1) == 0); i++) { reg_value >>= i; } memcpy(out_reg_value, (uint8_t *)®_value, MAX_REGISTER_BYTE_SIZE); } } else { uint8_t reg_val[MAX_REGISTER_BYTE_SIZE]; uint32_t val_offset = 0; auto closest_reg_offset = vex_to_unicorn_map.lower_bound(vex_reg_offset); if (from_saved_context) { uc_context_reg_read(saved_regs, vex_to_unicorn_map.at(closest_reg_offset->first).first, reg_val); } else { uc_reg_read(uc, vex_to_unicorn_map.at(closest_reg_offset->first).first, reg_val); } if (closest_reg_offset->first != vex_reg_offset) { // Dependency is a sub-register that starts in middle of larger register. Adjust offset to start copying // from closest_reg_offset--; val_offset = vex_reg_offset - closest_reg_offset->first; } memcpy(out_reg_value, reg_val + val_offset, MAX_REGISTER_BYTE_SIZE - val_offset); } return; } processed_vex_expr_t State::process_vex_expr(IRExpr *expr, IRTypeEnv *vex_block_tyenv, address_t instr_addr, int64_t curr_stmt_idx, const std::unordered_map &entity_setter, bool is_exit_stmt) { processed_vex_expr_t result; result.reset(); switch (expr->tag) { case Iex_RdTmp: { taint_entity_t taint_entity; taint_entity.entity_type = TAINT_ENTITY_TMP; taint_entity.tmp_id = expr->Iex.RdTmp.tmp; taint_entity.instr_addr = instr_addr; taint_entity.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); auto entry = entity_setter.find(taint_entity); if (entry != entity_setter.end()) { taint_entity.stmt_idx = entry->second; } result.taint_sources.emplace(taint_entity); result.value_size = taint_entity.value_size; break; } case Iex_Get: { taint_entity_t taint_entity; taint_entity.entity_type = TAINT_ENTITY_REG; taint_entity.reg_offset = expr->Iex.Get.offset; taint_entity.instr_addr = instr_addr; taint_entity.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); auto entry = entity_setter.find(taint_entity); if (entry != entity_setter.end()) { taint_entity.stmt_idx = entry->second; } result.taint_sources.emplace(taint_entity); result.value_size = taint_entity.value_size; break; } case Iex_Unop: { if (fp_ops_to_avoid.count(expr->Iex.Unop.op) > 0) { // Do not propagate taint from this expression since floating points support is not enabled result.has_floating_point_op_to_skip = true; break; } auto temp = process_vex_expr(expr->Iex.Unop.arg, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv);; break; } case Iex_Binop: { if (fp_ops_to_avoid.count(expr->Iex.Binop.op) > 0) { // Do not propagate taint from this expression since floating points support is not enabled result.has_floating_point_op_to_skip = true; break; } auto temp = process_vex_expr(expr->Iex.Binop.arg1, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.Binop.arg2, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv);; break; } case Iex_Triop: { if (fp_ops_to_avoid.count(expr->Iex.Triop.details->op) > 0) { // Do not propagate taint from this expression since floating points support is not enabled result.has_floating_point_op_to_skip = true; break; } auto temp = process_vex_expr(expr->Iex.Triop.details->arg1, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.Triop.details->arg2, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.Triop.details->arg3, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); break; } case Iex_Qop: { if (fp_ops_to_avoid.count(expr->Iex.Qop.details->op) > 0) { // Do not propagate taint from this expression since floating points support is not enabled result.has_floating_point_op_to_skip = true; break; } auto temp = process_vex_expr(expr->Iex.Qop.details->arg1, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.Qop.details->arg2, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.Qop.details->arg3, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.Qop.details->arg4, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); break; } case Iex_ITE: { // We store the taint entities in the condition for ITE separately in order to check // if condition is symbolic and stop concrete execution if it is. However for VEX // exit statement, we don't need to store it separately since we process only the // guard condition for Exit statements auto temp = process_vex_expr(expr->Iex.ITE.cond, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } if (is_exit_stmt) { result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.taint_sources.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); } else { result.ite_cond_entities.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); } result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.ITE.iffalse, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; temp = process_vex_expr(expr->Iex.ITE.iftrue, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); break; } case Iex_CCall: { IRExpr **ccall_args = expr->Iex.CCall.args; for (auto i = 0; ccall_args[i]; i++) { auto temp = process_vex_expr(ccall_args[i], vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); result.ite_cond_entities.insert(temp.ite_cond_entities.begin(), temp.ite_cond_entities.end()); result.mem_read_size += temp.mem_read_size; } result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); break; } case Iex_Load: { auto temp = process_vex_expr(expr->Iex.Load.addr, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, false); if (temp.has_unsupported_expr) { result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = temp.unsupported_expr_stop_reason; break; } // TODO: What if memory addresses have ITE expressions in them? taint_entity_t source; source.entity_type = TAINT_ENTITY_MEM; source.mem_ref_entity_list.insert(source.mem_ref_entity_list.end(), temp.taint_sources.begin(), temp.taint_sources.end()); source.instr_addr = instr_addr; source.sink_stmt_idx = curr_stmt_idx; result.taint_sources.emplace(source); // Calculate number of bytes read. unicorn sometimes triggers read hook multiple times for the same read result.mem_read_size += temp.mem_read_size; // TODO: Will there be a 1 bit read from memory? auto load_size = sizeofIRType(expr->Iex.Load.ty); result.mem_read_size += load_size; result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); break; } case Iex_GetI: { // GetI statement cannot be handled in VEX since exact register's offset is computed at runtime. result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = STOP_UNSUPPORTED_EXPR_GETI; if (fp_ops_to_avoid.size() > 0) { auto temp = process_vex_expr(expr->Iex.GetI.ix, vex_block_tyenv, instr_addr, curr_stmt_idx, entity_setter, is_exit_stmt); // ix will be an RdTmp expression. result.taint_sources.insert(temp.taint_sources.begin(), temp.taint_sources.end()); } break; } case Iex_Const: { result.value_size = get_vex_expr_result_size(expr, vex_block_tyenv); break; } case Iex_VECRET: case Iex_GSPTR: case Iex_Binder: break; default: { fprintf(stderr, "[sim_unicorn] Unsupported expression type encountered: %u\n", expr->tag); result.has_unsupported_expr = true; result.unsupported_expr_stop_reason = STOP_UNSUPPORTED_EXPR_UNKNOWN; break; } } return result; } // Determine cumulative result of taint statuses of a set of taint entities // EG: This is useful to determine the taint status of a taint sink given it's taint sources taint_status_result_t State::get_final_taint_status(const std::unordered_set &taint_sources) const { bool is_symbolic = false; for (auto &taint_source: taint_sources) { if (taint_source.entity_type == TAINT_ENTITY_NONE) { continue; } else if ((taint_source.entity_type == TAINT_ENTITY_REG) && (is_symbolic_register(taint_source.reg_offset, taint_source.value_size))) { // Register is symbolic. Continue checking for read from symbolic address is_symbolic = true; } else if ((taint_source.entity_type == TAINT_ENTITY_TMP) && (is_symbolic_temp(taint_source.tmp_id))) { // Temp is symbolic. Continue checking for read from a symbolic address is_symbolic = true; } else if (taint_source.entity_type == TAINT_ENTITY_MEM) { // Check if the memory address being read from is symbolic auto mem_address_status = get_final_taint_status(taint_source.mem_ref_entity_list); if (mem_address_status == TAINT_STATUS_SYMBOLIC) { // Address is symbolic. We have to stop concrete execution and so can stop analysing return TAINT_STATUS_DEPENDS_ON_READ_FROM_SYMBOLIC_ADDR; } else { // Address is concrete so we check result of the memory read mem_read_result_t mem_read_result; try { mem_read_result = block_mem_reads_map.at(taint_source.sink_stmt_idx); } catch (std::out_of_range const&) { assert(false && "[sim_unicorn] Taint sink depends on a read not executed yet! This should not happen!"); } is_symbolic = mem_read_result.is_mem_read_symbolic; } } } if (is_symbolic) { return TAINT_STATUS_SYMBOLIC; } return TAINT_STATUS_CONCRETE; } // A vector version of get_final_taint_status for checking mem_ref_entity_list which can't be an // unordered_set taint_status_result_t State::get_final_taint_status(const std::vector &taint_sources) const { std::unordered_set taint_sources_set(taint_sources.begin(), taint_sources.end()); return get_final_taint_status(taint_sources_set); } int32_t State::get_vex_expr_result_size(IRExpr *expr, IRTypeEnv* tyenv) const { auto expr_type = typeOfIRExpr(tyenv, expr); if (expr_type == Ity_I1) { return 0; } return sizeofIRType(expr_type); } bool State::is_cpuid_in_block(address_t block_address, int32_t block_size) { bool found_cpuid_bytes = false; int32_t real_size; int32_t i; const uint8_t cpuid_bytes[] = {0xf, 0xa2}; auto block_entry = block_taint_cache.find(block_address); if (block_entry != block_taint_cache.end()) { // VEX statements of block have been processed already. return block_entry->second.has_cpuid_instr; } // Assume block size is MAX_BB_SIZE if block size is report as 0. // See State::step real_size = block_size == 0 ? MAX_BB_SIZE : block_size; std::unique_ptr instructions(new uint8_t[real_size]); uc_mem_read(this->uc, block_address, instructions.get(), real_size); // Test 1: Look for bytes corresponding to the cpuid instruction(0fa2) in the block. Naive linear search for two // byte pattern i = 0; while (i < real_size) { if (instructions[i] == cpuid_bytes[0]) { if (instructions[i + 1] == cpuid_bytes[1]) { found_cpuid_bytes = true; break; } i ++; } i++; } if (!found_cpuid_bytes) { return false; } // Test 2: Verify using VEX statements of the block. If we reached here, then block is certainly not already lifted // to VEX. Let's process them. auto vex_lift_result = lift_block(block_address, real_size); if ((vex_lift_result == NULL) || (vex_lift_result->size == 0)) { // Since VEX lift failed, we cannot verify if cpuid is present. Assume it could exist and stop emulation. stop(STOP_VEX_LIFT_FAILED); return true; } process_vex_block(vex_lift_result->irsb, block_address); block_entry = block_taint_cache.find(block_address); if (block_size == 0) { // Remove block from block taint cache since size reported by unicorn is 0 block_taint_cache.erase(block_entry); } return block_entry->second.has_cpuid_instr; } VEXLiftResult* State::lift_block(address_t block_address, int32_t block_size) { // Using the optimized VEX block causes write-write conflicts: an older value becomes current value because the // corresponding instruction is executed as dependency of a symbolic instruction to set some VEX temps. Thus, we use // the unoptimized VEX block. VexRegisterUpdates pxControl = VexRegUpdUnwindregsAtMemAccess; address_t lift_address; uint8_t *insn_start; if ((arch == UC_ARCH_ARM) && is_thumb_mode()) { // vex_lift's Thumb decoder uses the bytes_offset/lookback convention: insn_start points to the byte at // (block_address + 1) (the byte at insn_addr with the Thumb bit set), and the byte at the physical block start // is reached via the lookback region. Allocate pre/post slack so the Thumb lifter can safely peek beyond the // block (e.g., when checking for surrounding IT instructions or 32-bit Thumb-2 second halfwords), and so that // vex_lift can consume up to block_size bytes from insn_start. const int pre_slack = 8; const int post_slack = 8; std::unique_ptr instructions(new uint8_t[pre_slack + block_size + post_slack]); std::memset(instructions.get(), 0, pre_slack + block_size + post_slack); uc_mem_read(this->uc, block_address, instructions.get() + pre_slack, block_size); lift_address = block_address | 1; insn_start = instructions.get() + pre_slack + 1; return vex_lift(vex_guest, vex_archinfo, insn_start, lift_address, 99, block_size, 1 /* opt_level */, 0 /* traceflags */, 1 /* allow_arch_optimizations */, 1 /* strict_block_end */, 0 /* collect_data_refs */, 0 /* load_from_ro_regions */, 0 /* const_prop */, pxControl, pre_slack + 1 /* lookback */ ); } std::unique_ptr instructions(new uint8_t[block_size]); lift_address = block_address; uc_mem_read(this->uc, block_address, instructions.get(), block_size); insn_start = instructions.get(); return vex_lift(vex_guest, vex_archinfo, insn_start, lift_address, 99, block_size, 1 /* opt_level */, 0 /* traceflags */, 1 /* allow_arch_optimizations */, 1 /* strict_block_end */, 0 /* collect_data_refs */, 0 /* load_from_ro_regions */, 0 /* const_prop */, pxControl, 0 /* lookback */ ); } void State::mark_register_symbolic(vex_reg_offset_t reg_offset, int64_t reg_size) { // Mark register as symbolic in the state in current block if (is_blacklisted_register(reg_offset)) { return; } else if (cpu_flags.find(reg_offset) != cpu_flags.end()) { block_symbolic_registers.emplace(reg_offset); block_concrete_registers.erase(reg_offset); } else { for (auto i = 0; i < reg_size; i++) { block_symbolic_registers.emplace(reg_offset + i); block_concrete_registers.erase(reg_offset + i); } } return; } void State::mark_temp_symbolic(vex_tmp_id_t temp_id) { // Mark VEX temp as symbolic in current block block_symbolic_temps.emplace(temp_id); return; } void State::mark_register_concrete(vex_reg_offset_t reg_offset, int64_t reg_size) { // Mark register as concrete in the current block if (is_blacklisted_register(reg_offset)) { return; } else if (cpu_flags.find(reg_offset) != cpu_flags.end()) { block_symbolic_registers.erase(reg_offset); block_concrete_registers.emplace(reg_offset); } else { for (auto i = 0; i < reg_size; i++) { block_symbolic_registers.erase(reg_offset + i); block_concrete_registers.emplace(reg_offset + i); } } return; } bool State::is_symbolic_register(vex_reg_offset_t reg_offset, int64_t reg_size) const { // We check if this register is symbolic or concrete in the block level taint statuses since // those are more recent. If not found in either, check the state's symbolic register list. // TODO: Is checking only first byte of artificial and blacklisted registers to determine if they are symbolic fine // or should all be checked? if ((cpu_flags.find(reg_offset) != cpu_flags.end()) || (artificial_vex_registers.count(reg_offset) > 0) || (blacklisted_registers.count(reg_offset) > 0)) { if (block_symbolic_registers.count(reg_offset) > 0) { return true; } else if (block_concrete_registers.count(reg_offset) > 0) { return false; } else if (symbolic_registers.count(reg_offset) > 0) { return true; } return false; } // The register is not a CPU flag and so we check every byte of the register for (auto i = 0; i < reg_size; i++) { // If any of the register's bytes are symbolic, we deem the register to be symbolic if (block_symbolic_registers.count(reg_offset + i) > 0) { return true; } } bool is_concrete = true; for (auto i = 0; i < reg_size; i++) { if (block_concrete_registers.count(reg_offset) == 0) { is_concrete = false; break; } } if (is_concrete) { // All bytes of register are concrete and so the register is concrete return false; } // If we reach here, it means that the register is not marked symbolic or concrete in the block // level taint status tracker. We check the state's symbolic register list. for (auto i = 0; i < reg_size; i++) { if (symbolic_registers.count(reg_offset + i) > 0) { return true; } } return false; } bool State::is_symbolic_temp(vex_tmp_id_t temp_id) const { return (block_symbolic_temps.count(temp_id) > 0); } void State::propagate_taints() { if (is_symbolic_tracking_disabled()) { // We're not checking symbolic registers so no need to propagate taints return; } auto& block_taint_entry = this->block_taint_cache.at(curr_block_details.block_addr); if (((symbolic_registers.size() > 0) || (block_symbolic_registers.size() > 0)) && block_taint_entry.has_unsupported_stmt_or_expr_type) { // There are symbolic registers and VEX statements in block for which taint propagation // is not supported. Stop concrete execution. stop(block_taint_entry.unsupported_stmt_stop_reason); return; } // Resume propagating taints using symbolic_registers and symbolic_temps from where we paused std::map::iterator stmt_taint_data_entries_it; if (taint_engine_next_stmt_idx != -1) { stmt_taint_data_entries_it = block_taint_entry.block_stmts_taint_data.find(taint_engine_next_stmt_idx); } else { stmt_taint_data_entries_it = block_taint_entry.block_stmts_taint_data.begin(); } auto stmt_taint_data_stop_it = block_taint_entry.block_stmts_taint_data.end(); // We continue propagating taint until we encounter 1) a memory read, 2) end of block or // 3) a stop state for concrete execution for (; stmt_taint_data_entries_it != stmt_taint_data_stop_it && !stopped; ++stmt_taint_data_entries_it) { auto curr_stmt_idx = stmt_taint_data_entries_it->first; auto &curr_stmt_taint_data = stmt_taint_data_entries_it->second; address_t curr_instr_addr = curr_stmt_taint_data.sink.instr_addr; std::unordered_map concrete_reg_deps; // Save list of register dependencies of current instruction which are concrete for slice computation later for (auto &dep: curr_stmt_taint_data.sources) { if ((dep.entity_type == TAINT_ENTITY_REG) && is_valid_dependency_register(dep.reg_offset) && !is_symbolic_register(dep.reg_offset, dep.value_size)) { concrete_reg_deps.emplace(std::make_pair(dep.reg_offset, dep.value_size)); } } block_stmt_concrete_regs.emplace(curr_stmt_idx, concrete_reg_deps); if (curr_stmt_taint_data.has_memory_read) { // Pause taint propagation to process the memory read and continue from instruction // after the memory read. taint_engine_stop_mem_read_instruction = curr_instr_addr; taint_engine_stop_mem_read_size = curr_stmt_taint_data.mem_read_size; taint_engine_next_stmt_idx = curr_stmt_idx; return; } if ((symbolic_registers.size() == 0) && (block_symbolic_registers.size() == 0) && (block_symbolic_temps.size() == 0)) { // There are no symbolic registers so no taint to propagate. Mark any memory writes // as concrete and update slice of registers. if (curr_stmt_taint_data.mem_write_size != 0) { block_mem_writes_taint_data.emplace_back(curr_instr_addr, false, curr_stmt_taint_data.mem_write_size); } continue; } propagate_taint_of_one_stmt(curr_stmt_taint_data); } // If we reached here, execution has reached the end of the block if (!stopped && !handle_symbolic_conditions && is_block_next_target_symbolic()) { stop(STOP_SYMBOLIC_BLOCK_EXIT_TARGET); } return; } void State::propagate_taint_of_mem_read_instr_and_continue(address_t read_address, int read_size) { std::vector memory_read_values; address_t curr_instr_addr; int64_t curr_stmt_idx; bool is_mem_read_symbolic; is_mem_read_symbolic = (find_tainted(read_address, read_size) != -1); if (is_symbolic_tracking_disabled()) { if (is_mem_read_symbolic) { // Symbolic register tracking is disabled but memory location has symbolic data. // We switch to VEX engine then. stop(STOP_SYMBOLIC_READ_SYMBOLIC_TRACKING_DISABLED); return; } // We're not checking symbolic registers so no need to propagate taints return; } // Save info about the memory read for (auto i = 0; i < read_size; i++) { memory_value_t memory_value; memory_value.address = read_address + i; if (!is_mem_read_symbolic) { memory_value.is_value_symbolic = false; } else { memory_value.is_value_symbolic = (find_tainted(read_address + i, 1) != -1); } memory_read_values.emplace_back(memory_value); } if (!is_mem_read_symbolic && !symbolic_read_in_progress && (symbolic_registers.size() == 0) && (block_symbolic_registers.size() == 0) && (block_symbolic_temps.size() == 0)) { // The value read from memory is concrete and there are no symbolic registers or VEX temps. No need to propagate // taint. Since we cannot rely on the unicorn engine to find out current instruction correctly, we simply save // the memory read value in a list for now and rebuild the map later if needed using instruction info from VEX // block block_mem_reads_data.insert(block_mem_reads_data.end(), memory_read_values.begin(), memory_read_values.end()); return; } if (block_taint_cache.find(curr_block_details.block_addr) == block_taint_cache.end()) { // The VEX statements of current block has not been processed yet. This means symbolic taint is being introduced // by this memory read. Let's process the block, rebuild its memory reads map and find the current instruction // address curr_block_details.vex_lift_result = lift_block(curr_block_details.block_addr, curr_block_details.block_size); if ((curr_block_details.vex_lift_result == NULL) || (curr_block_details.vex_lift_result->size == 0) || (curr_block_details.vex_lift_result->size != curr_block_details.block_size)) { // Failed to lift block to VEX. if (is_mem_read_symbolic) { // Since we are processing VEX block for the first time, there are no symbolic registers/VEX temps. // Thus, it is sufficient to check if the value read from memory is symbolic. stop(STOP_VEX_LIFT_FAILED); } else { // There are no symbolic registers so let's attempt to execute the block. curr_block_details.vex_lift_failed = true; } return; } process_vex_block(curr_block_details.vex_lift_result->irsb, curr_block_details.block_addr); } auto& block_taint_entry = block_taint_cache.at(curr_block_details.block_addr); if (taint_engine_stop_mem_read_instruction != 0) { // Taint has been propagated and so we can rely on information from taint engine to find current instruction // address and hence update the block's memory reads map curr_instr_addr = taint_engine_stop_mem_read_instruction; curr_stmt_idx = taint_engine_next_stmt_idx; } else { // Symbolic taint is being introduced by this memory read so we cannot rely on taint engine to find current // instruction address auto vex_stmt_taint_entry_it = block_taint_entry.block_stmts_taint_data.begin(); if (block_mem_reads_data.size() > 0) { // There are previous reads that need to be insert into block's memory reads map while (vex_stmt_taint_entry_it != block_taint_entry.block_stmts_taint_data.end()) { if (vex_stmt_taint_entry_it->second.has_memory_read) { mem_read_result_t mem_read_result; bool is_addr_set = false; while (block_mem_reads_data.size() != 0) { auto &next_mem_read = block_mem_reads_data.front(); if (!is_addr_set) { mem_read_result.first_byte_addr = next_mem_read.address; is_addr_set = true; } mem_read_result.memory_values.emplace_back(next_mem_read); mem_read_result.is_mem_read_symbolic |= next_mem_read.is_value_symbolic; mem_read_result.read_size += 1; block_mem_reads_data.erase(block_mem_reads_data.begin()); if (mem_read_result.read_size == vex_stmt_taint_entry_it->second.mem_read_size) { block_mem_reads_map.emplace(vex_stmt_taint_entry_it->second.sink.stmt_idx, mem_read_result); for (auto &value: mem_read_result.memory_values) { if (!value.is_value_symbolic) { auto entry = block_mem_read_addr_details.find(value.address); if (entry == block_mem_read_addr_details.end()) { block_mem_read_addr_details.emplace(value.address, std::unordered_set({vex_stmt_taint_entry_it->second.sink.stmt_idx})); } else { entry->second.emplace(vex_stmt_taint_entry_it->second.sink.stmt_idx); } } } break; } else if (block_mem_reads_data.size() == 0) { // This entry is of a partial memory read for the instruction being processed. block_mem_reads_map.emplace(vex_stmt_taint_entry_it->second.sink.stmt_idx, mem_read_result); for (auto &value: mem_read_result.memory_values) { if (!value.is_value_symbolic) { auto entry = block_mem_read_addr_details.find(value.address); if (entry == block_mem_read_addr_details.end()) { block_mem_read_addr_details.emplace(value.address, std::unordered_set({vex_stmt_taint_entry_it->second.sink.stmt_idx})); } else { entry->second.emplace(vex_stmt_taint_entry_it->second.sink.stmt_idx); } } } break; } } if (block_mem_reads_data.size() == 0) { // All pending reads have been processed and inserted into the map if (block_mem_reads_map.at(vex_stmt_taint_entry_it->second.sink.stmt_idx).read_size == vex_stmt_taint_entry_it->second.mem_read_size) { // Update iterator since all reads for current instruction have been processed. We should // start searching for next instruction with memory read from successor of this instruction. vex_stmt_taint_entry_it++; } break; } } vex_stmt_taint_entry_it++; } if ((block_mem_reads_data.size() != 0) && (vex_stmt_taint_entry_it == block_taint_entry.block_stmts_taint_data.end())) { // There are still some pending reads but all instructions in the block have been processed. Something // is wrong. assert(false && "There are pending memory reads to process but full block has been processed. This should not happen!"); } } // Find next instruction with memory read while (!vex_stmt_taint_entry_it->second.has_memory_read) { vex_stmt_taint_entry_it++; if (vex_stmt_taint_entry_it == block_taint_entry.block_stmts_taint_data.end()) { // Current read does not belong to any possible instruction in current block. This should not happen! assert(false && "Unable to identify instruction for current memory read. This should not happen!"); } } curr_instr_addr = vex_stmt_taint_entry_it->second.sink.instr_addr; curr_stmt_idx = vex_stmt_taint_entry_it->first; taint_engine_stop_mem_read_size = vex_stmt_taint_entry_it->second.mem_read_size; } auto mem_reads_map_entry = block_mem_reads_map.find(curr_stmt_idx); if (mem_reads_map_entry == block_mem_reads_map.end()) { mem_read_result_t mem_read_result; mem_read_result.first_byte_addr = memory_read_values[0].address; mem_read_result.memory_values = memory_read_values; mem_read_result.is_mem_read_symbolic = is_mem_read_symbolic; mem_read_result.read_size = read_size; block_mem_reads_map.emplace(curr_stmt_idx, mem_read_result); } else { auto &mem_read_entry = block_mem_reads_map.at(curr_stmt_idx); mem_read_entry.memory_values.insert(mem_read_entry.memory_values.end(), memory_read_values.begin(), memory_read_values.end()); mem_read_entry.is_mem_read_symbolic |= is_mem_read_symbolic; mem_read_entry.read_size += read_size; } for (auto &value: memory_read_values) { if (!value.is_value_symbolic) { auto entry = block_mem_read_addr_details.find(value.address); if (entry == block_mem_read_addr_details.end()) { block_mem_read_addr_details.emplace(value.address, std::unordered_set({curr_stmt_idx})); } else { entry->second.emplace(curr_stmt_idx); } } } // At this point the block's memory reads map has been rebuilt and we can propagate taint as before auto &mem_read_result = block_mem_reads_map.at(curr_stmt_idx); if (curr_block_details.vex_lift_failed) { if (mem_read_result.is_mem_read_symbolic || (symbolic_registers.size() > 0) || (block_symbolic_registers.size() > 0) || (block_symbolic_temps.size() > 0)) { // Either the memory value is symbolic or there are symbolic registers: thus, taint // status of registers could change. But since VEX lift failed, the taint relations // are not known and so we can't propagate taint. Stop concrete execution. stop(STOP_VEX_LIFT_FAILED); return; } else { // We cannot propagate taint since VEX lift failed and so we stop here. But, since // there are no symbolic values, we do need need to propagate taint. return; } } if (mem_read_result.read_size < taint_engine_stop_mem_read_size) { // There are more bytes to be read by this instruction. We do not propagate taint until bytes are read // Sometimes reads are split across multiple reads hooks in unicorn. // Also, remember that a symbolic value has been partially read from memory so that even if the rest of the // bytes to be read are concrete, taint will be propagated. symbolic_read_in_progress = true; taint_engine_stop_mem_read_instruction = curr_instr_addr; taint_engine_next_stmt_idx = curr_stmt_idx; return; } else if (mem_read_result.read_size > taint_engine_stop_mem_read_size) { // Somehow the read result has read more bytes than read operation should according to the VEX statements. // Likely a bug. assert(false && "Memory read operation has read more bytes than expected. This should not happen!"); } // Mark read as complete symbolic_read_in_progress = false; // There are no more pending reads at this instruction. Now we can propagate taint. // This allows us to also handle cases when only some of the memory reads are symbolic: we treat all as symbolic // and overtaint. auto& vex_stmt_taint_data_entry = block_taint_entry.block_stmts_taint_data.at(curr_stmt_idx); if (mem_read_result.is_mem_read_symbolic || (symbolic_registers.size() > 0) || (block_symbolic_registers.size() > 0) || block_symbolic_temps.size() > 0) { if (block_taint_entry.has_unsupported_stmt_or_expr_type) { // There are symbolic registers and/or memory read was symbolic and there are VEX // statements in block for which taint propagation is not supported. stop(block_taint_entry.unsupported_stmt_stop_reason); return; } propagate_taint_of_one_stmt(vex_stmt_taint_data_entry); } if (!stopped) { auto curr_stmt_entry = block_taint_entry.block_stmts_taint_data.find(curr_stmt_idx); if (curr_stmt_entry != block_taint_entry.block_stmts_taint_data.end()) { taint_engine_next_stmt_idx = std::next(curr_stmt_entry)->first; continue_propagating_taint(); } } return; } void State::propagate_taint_of_one_stmt(const vex_stmt_taint_entry_t &vex_stmt_taint_entry) { vex_stmt_details_t vex_stmt_details; bool is_stmt_symbolic; bool sets_vex_cc_reg; auto &taint_sink = vex_stmt_taint_entry.sink; if (vex_stmt_taint_entry.floating_point_op_skip) { // Since statement contains floating point operation that should be skipped, mark sink as concrete and stop. if (taint_sink.entity_type == TAINT_ENTITY_REG) { mark_register_concrete(taint_sink.reg_offset, taint_sink.value_size); } return; } auto &taint_srcs = vex_stmt_taint_entry.sources; if ((taint_sink.entity_type == TAINT_ENTITY_REG) && (vex_cc_regs.find(taint_sink.reg_offset) != vex_cc_regs.end())) { sets_vex_cc_reg = true; } else { sets_vex_cc_reg = false; } is_stmt_symbolic = false; vex_stmt_details = compute_vex_stmt_details(vex_stmt_taint_entry); if (taint_sink.entity_type == TAINT_ENTITY_MEM) { vex_stmt_details.has_memory_write = true; auto addr_taint_status = get_final_taint_status(taint_sink.mem_ref_entity_list); // Check if address written to is symbolic or is read from memory if (addr_taint_status != TAINT_STATUS_CONCRETE) { if (handle_symbolic_addrs) { is_stmt_symbolic = true; } else { stop(STOP_SYMBOLIC_WRITE_ADDR); return; } } auto sink_taint_status = get_final_taint_status(taint_srcs); if (sink_taint_status == TAINT_STATUS_DEPENDS_ON_READ_FROM_SYMBOLIC_ADDR) { if (handle_symbolic_addrs) { is_stmt_symbolic = true; sink_taint_status = TAINT_STATUS_SYMBOLIC; vex_stmt_details.has_read_from_symbolic_addr = true; } else { stop(STOP_SYMBOLIC_READ_ADDR); return; } } if ((sink_taint_status == TAINT_STATUS_SYMBOLIC) || (addr_taint_status == TAINT_STATUS_SYMBOLIC)) { // Save the memory location written to be marked as symbolic in write hook block_mem_writes_taint_data.emplace_back(taint_sink.instr_addr, true, taint_sink.value_size); // Mark instruction as needing symbolic execution is_stmt_symbolic = true; } else { // Save the memory location(s) written to be marked as concrete in the write // hook only if it is not a previously seen write block_mem_writes_taint_data.emplace_back(taint_sink.instr_addr, false, taint_sink.value_size); } } else if ((taint_sink.entity_type != TAINT_ENTITY_NONE) || vex_stmt_taint_entry.is_exit) { taint_status_result_t final_taint_status = get_final_taint_status(taint_srcs); if (final_taint_status == TAINT_STATUS_DEPENDS_ON_READ_FROM_SYMBOLIC_ADDR) { if (handle_symbolic_addrs) { is_stmt_symbolic = true; final_taint_status = TAINT_STATUS_SYMBOLIC; vex_stmt_details.has_read_from_symbolic_addr = true; } else { stop(STOP_SYMBOLIC_READ_ADDR); return; } } else if (final_taint_status != TAINT_STATUS_CONCRETE) { if ((taint_sink.entity_type == TAINT_ENTITY_REG) && (taint_sink.reg_offset == arch_pc_reg_vex_offset())) { stop(STOP_SYMBOLIC_PC); return; } // Mark instruction as needing symbolic execution is_stmt_symbolic = true; // Mark sink as symbolic if (taint_sink.entity_type == TAINT_ENTITY_REG) { mark_register_symbolic(taint_sink.reg_offset, taint_sink.value_size); } else { mark_temp_symbolic(taint_sink.tmp_id); } } else if ((taint_sink.entity_type == TAINT_ENTITY_REG) && (taint_sink.reg_offset != arch_pc_reg_vex_offset())) { // Mark register as concrete since none of it's dependencies are symbolic. mark_register_concrete(taint_sink.reg_offset, taint_sink.value_size); } } auto ite_cond_taint_status = get_final_taint_status(vex_stmt_taint_entry.ite_cond_entity_list); if (ite_cond_taint_status != TAINT_STATUS_CONCRETE) { is_stmt_symbolic = true; } if (sets_vex_cc_reg) { if (curr_block_details.marks_vex_cc_reg_symbolic) { // Some previous statement sets a VEX CC register to symbolic. Add this statement to list of statements that // need to be re-executed curr_block_details.symbolic_stmts.emplace_back(vex_stmt_details); } else if (!is_stmt_symbolic) { // This statement sets a VEX CC register but is not symbolic. Save its details in case needed later. curr_block_details.vex_cc_reg_setter_details.emplace_back(vex_stmt_details); } // This is the first state that sets a VEX CC register to a symbolic value. Mark previous statements that // touch a VEX CC register as symbolic for correct re-execution else { curr_block_details.marks_vex_cc_reg_symbolic = true; if (curr_block_details.symbolic_stmts.size() == 0) { // There are no symbolic statements. Simply insert all VEX CC register setters into list of symbolic // statements. curr_block_details.symbolic_stmts.insert(curr_block_details.symbolic_stmts.end(), curr_block_details.vex_cc_reg_setter_details.begin(), curr_block_details.vex_cc_reg_setter_details.end()); } else { for (auto &stmt_detail: curr_block_details.vex_cc_reg_setter_details) { auto sym_stmt_it = curr_block_details.symbolic_stmts.begin(); auto sym_stmt_it_end = curr_block_details.symbolic_stmts.end(); for (; sym_stmt_it != sym_stmt_it_end; sym_stmt_it++) { if (stmt_detail.stmt_idx < sym_stmt_it->stmt_idx) { curr_block_details.symbolic_stmts.insert(sym_stmt_it - 1, stmt_detail); break; } } } } curr_block_details.symbolic_stmts.emplace_back(vex_stmt_details); curr_block_details.vex_cc_reg_setter_details.clear(); } } else if (is_stmt_symbolic) { if (vex_stmt_taint_entry.is_exit) { if (handle_symbolic_conditions) { curr_block_details.has_symbolic_exit = true; } else { stop(STOP_SYMBOLIC_BLOCK_EXIT_CONDITION); return; } } if (vex_stmt_details.has_symbolic_memory_dep) { for (auto &mem_value: block_mem_reads_map.at(taint_sink.stmt_idx).memory_values) { if (mem_value.is_value_symbolic) { block_symbolic_mem_deps.emplace(mem_value.address); } } } if ((taint_sink.entity_type == TAINT_ENTITY_REG) || (taint_sink.entity_type == TAINT_ENTITY_MEM) || vex_stmt_taint_entry.is_exit) { curr_block_details.symbolic_stmts.emplace_back(vex_stmt_details); } } return; } vex_stmt_details_t State::compute_vex_stmt_details(const vex_stmt_taint_entry_t &vex_stmt_taint_entry) { vex_stmt_details_t stmt_details; stmt_details.instr_addr = vex_stmt_taint_entry.sink.instr_addr; stmt_details.stmt_idx = vex_stmt_taint_entry.sink.stmt_idx; if (vex_stmt_taint_entry.has_memory_read) { auto mem_read_result = block_mem_reads_map.at(stmt_details.stmt_idx); if (!mem_read_result.is_mem_read_symbolic) { stmt_details.has_concrete_memory_dep = true; stmt_details.has_symbolic_memory_dep = false; } else { stmt_details.has_concrete_memory_dep = false; stmt_details.has_symbolic_memory_dep = true; } } else { stmt_details.has_concrete_memory_dep = false; stmt_details.has_symbolic_memory_dep = false; } return stmt_details; } void State::start_propagating_taint() { address_t block_address = curr_block_details.block_addr; int32_t block_size = curr_block_details.block_size; curr_block_details.block_trace_ind = executed_blocks_count; if (is_symbolic_tracking_disabled()) { // We're not checking symbolic registers so no need to propagate taints return; } if ((arch == UC_ARCH_ARM) && (block_taint_cache.find(block_address) == block_taint_cache.end())) { // Block was not lifted and processed before. So it could end in syscall curr_block_details.vex_lift_result = lift_block(block_address, block_size); if ((curr_block_details.vex_lift_result == NULL) || (curr_block_details.vex_lift_result->size == 0) || (curr_block_details.vex_lift_result->size != curr_block_details.block_size)) { // Failed to lift block to VEX. We don't execute the block because it could end in a syscall. stop(STOP_VEX_LIFT_FAILED); return; } if (curr_block_details.vex_lift_result->irsb->jumpkind == Ijk_Sys_syscall) { // This block invokes a syscall. For now, such blocks are handled by VEX engine. stop(STOP_SYSCALL_ARM); return; } } if ((arch == UC_ARCH_X86) && is_cpuid_in_block(block_address, block_size)) { // Check if emulation was stopped; could be if VEX lift failed if (!stopped) { stop(STOP_X86_CPUID); } return; } block_symbolic_temps.clear(); if (symbolic_registers.size() != 0) { if (block_taint_cache.find(block_address) == block_taint_cache.end()) { // Compute and cache taint sink-source relations for this block since there are symbolic registers. if (curr_block_details.vex_lift_result == NULL) { curr_block_details.vex_lift_result = lift_block(block_address, block_size); if ((curr_block_details.vex_lift_result == NULL) || (curr_block_details.vex_lift_result->size == 0) || (curr_block_details.vex_lift_result->size != curr_block_details.block_size)) { // There are symbolic registers but VEX lift failed so we can't propagate taint stop(STOP_VEX_LIFT_FAILED); return; } } process_vex_block(curr_block_details.vex_lift_result->irsb, block_address); } propagate_taints(); } return; } void State::continue_propagating_taint() { if (is_symbolic_tracking_disabled()) { // We're not checking symbolic registers so no need to propagate taints return; } if (curr_block_details.vex_lift_failed) { if ((symbolic_registers.size() > 0) || (block_symbolic_registers.size() > 0)) { // There are symbolic registers but VEX lift failed so we can't propagate taint stop(STOP_VEX_LIFT_FAILED); return; } } else { propagate_taints(); } return; } void State::save_concrete_memory_deps(vex_stmt_details_t &vex_stmt_det) { if (vex_stmt_det.has_concrete_memory_dep || (vex_stmt_det.has_symbolic_memory_dep && !vex_stmt_det.has_read_from_symbolic_addr)) { auto &mem_read_result = block_mem_reads_map.at(vex_stmt_det.stmt_idx); save_mem_values(mem_read_result); archived_memory_values.emplace_back(mem_read_result.memory_values); vex_stmt_det.memory_values = &(archived_memory_values.back()[0]); vex_stmt_det.memory_values_count = archived_memory_values.back().size(); } std::queue::iterator> vex_stmts_to_process; for (auto it = vex_stmt_det.stmt_deps.begin(); it != vex_stmt_det.stmt_deps.end(); it++) { vex_stmts_to_process.push(it); } while (!vex_stmts_to_process.empty()) { auto &curr_stmt = vex_stmts_to_process.front(); if ((curr_stmt->has_concrete_memory_dep) || (curr_stmt->has_symbolic_memory_dep && !curr_stmt->has_read_from_symbolic_addr)) { auto &mem_read_result = block_mem_reads_map.at(curr_stmt->stmt_idx); save_mem_values(mem_read_result); archived_memory_values.emplace_back(mem_read_result.memory_values); curr_stmt->memory_values = &(archived_memory_values.back()[0]); curr_stmt->memory_values_count = archived_memory_values.back().size(); } vex_stmts_to_process.pop(); for (auto it = curr_stmt->stmt_deps.begin(); it != curr_stmt->stmt_deps.end(); *it++) { vex_stmts_to_process.push(it); } } return; } void State::save_mem_values(mem_read_result_t &mem_read_result) { std::vector mem_bytes; mem_bytes.reserve(mem_read_result.read_size); uc_mem_read(uc, mem_read_result.first_byte_addr, mem_bytes.data(), mem_read_result.read_size); for (auto &mem_value: mem_read_result.memory_values) { if (!mem_value.is_value_symbolic && !mem_value.is_value_set) { mem_value.value = mem_bytes[mem_value.address - mem_read_result.first_byte_addr]; } } return; } bool State::is_block_next_target_symbolic() const { auto& block_taint_entry = block_taint_cache.at(curr_block_details.block_addr); auto block_next_target_taint_status = get_final_taint_status(block_taint_entry.block_next_entities); return (block_next_target_taint_status != TAINT_STATUS_CONCRETE); } void State::set_curr_block_details(address_t block_address, int32_t block_size) { curr_block_details.block_addr = block_address; curr_block_details.block_size = block_size; return; } address_t State::get_instruction_pointer() const { address_t out = 0; int reg = arch_pc_reg(); if (reg == -1) { out = 0; } else { uc_reg_read(uc, reg, &out); } return out; } address_t State::get_stack_pointer() const { address_t out = 0; int reg = arch_sp_reg(); if (reg == -1) { out = 0; } else { uc_reg_read(uc, reg, &out); } return out; } void State::fd_init_bytes(uint64_t fd, char *bytes, taint_t *taints, uint64_t len, uint64_t read_pos) { fd_details.emplace(fd, fd_data(bytes, taints, len, read_pos)); return; } uint64_t State::fd_read(uint64_t fd, char *buf, taint_t *&taints, uint64_t count) { auto &fd_det = fd_details.at(fd); if (fd_det.curr_pos >= fd_det.len) { // No more bytes to read return 0; } // Truncate count of bytes to read if request exceeds number left in the "stream" auto actual_count = std::min(count, fd_det.len - fd_det.curr_pos); memcpy(buf, fd_det.bytes + fd_det.curr_pos, actual_count); taints = fd_det.taints + fd_det.curr_pos; fd_det.curr_pos += actual_count; return actual_count; } void State::init_random_bytes(uint64_t *values, uint64_t *sizes, uint64_t count) { for (auto i = 0; i < count; i++) { random_bytes.emplace_back(values[i], sizes[i]); } return; } // CGC syscall handlers void State::perform_cgc_random() { uint32_t buf, count, rnd_bytes; uint64_t number_of_items_to_process, actual_count, next_write_offset; char *rand_bytes; uc_reg_read(uc, UC_X86_REG_EBX, &buf); uc_reg_read(uc, UC_X86_REG_ECX, &count); uc_reg_read(uc, UC_X86_REG_EDX, &rnd_bytes); if (count == 0) { if (rnd_bytes != 0) { handle_write(rnd_bytes, 4, true); if (stopped) { return; } uc_mem_write(uc, rnd_bytes, &count, 4); } uc_reg_write(uc, UC_X86_REG_EAX, &count); interrupt_handled = true; syscall_count++; return; } number_of_items_to_process = 0; actual_count = 0; for (auto &val: random_bytes) { if (actual_count == count) { break; } actual_count += val.second; number_of_items_to_process++; } assert((actual_count == count)); rand_bytes = (char *)malloc(actual_count); next_write_offset = 0; for (auto i = 0; i < number_of_items_to_process; i++) { std::reverse_copy((char *)&(random_bytes[i].first), (char *)&(random_bytes[i].first) + random_bytes[i].second, rand_bytes + next_write_offset); next_write_offset += random_bytes[i].second; } for (auto i = 0; i < number_of_items_to_process; i++) { random_bytes.erase(random_bytes.begin()); } handle_write(buf, actual_count, true, true); if (stopped) { free(rand_bytes); return; } uc_mem_write(uc, buf, rand_bytes, actual_count); free(rand_bytes); if (rnd_bytes != 0) { handle_write(rnd_bytes, 4, true); if (stopped) { return; } uc_mem_write(uc, rnd_bytes, &actual_count, 4); } next_write_offset = 0; uc_reg_write(uc, UC_X86_REG_EAX, &next_write_offset); step(cgc_random_bbl, 0, false); commit(); if (actual_count > 0) { // Save a block with an instruction to track that the random syscall needs to be re-executed. The instruction // data is used only to work with existing mechanism to return data to python. // Save all non-symbolic register arguments needed for syscall. block_details_t block_for_random; block_for_random.block_addr = cgc_random_bbl; block_for_random.block_size = 0; block_for_random.block_trace_ind = executed_blocks_count; block_for_random.has_symbolic_exit = false; vex_stmt_details_t stmt_for_random; // First argument: ebx register_value_t reg_val; if (!is_symbolic_register(20, 4)) { reg_val.offset = 20; reg_val.size = 4; get_register_value(reg_val.offset, reg_val.value); stmt_for_random.reg_deps.emplace(reg_val); } // Second argument: ecx if (!is_symbolic_register(12, 4)) { reg_val.offset = 12; reg_val.size = 4; get_register_value(reg_val.offset, reg_val.value); stmt_for_random.reg_deps.emplace(reg_val); } block_for_random.symbolic_stmts.emplace_back(stmt_for_random); blocks_with_symbolic_stmts.emplace_back(block_for_random); } interrupt_handled = true; syscall_count++; return; } void State::perform_cgc_receive() { uint32_t fd, buf, count, rx_bytes; uc_reg_read(uc, UC_X86_REG_EBX, &fd); if (fd > 2) { // Ignore any fds > 2 interrupt_handled = true; return; } if (fd == 1) { // Python land maps 1 to 0 so do the same here for receives run on stdout. fd = 0; } if (fd_details.count(fd) == 0) { // fd stream has not been initialized in native interface. Can't perform receive. return; } uc_reg_read(uc, UC_X86_REG_ECX, &buf); uc_reg_read(uc, UC_X86_REG_EDX, &count); uc_reg_read(uc, UC_X86_REG_ESI, &rx_bytes); if (count == 0) { // Requested to read 0 bytes. Set *rx_bytes and syscall return value to 0 if (rx_bytes != 0) { handle_write(rx_bytes, 4, true); if (stopped) { return; } uc_mem_write(uc, rx_bytes, &count, 4); } uc_reg_write(uc, UC_X86_REG_EAX, &count); interrupt_handled = true; syscall_count++; return; } if ((cgc_receive_max_size != 0) && (count > cgc_receive_max_size)) { count = cgc_receive_max_size; } // Perform read char *tmp_buf = (char *)malloc(count); taint_t *tmp_taint_buf; auto actual_count = fd_read(fd, tmp_buf, tmp_taint_buf, count); if (stopped) { // Possibly stopped when writing bytes read to memory. Treat as syscall failure. free(tmp_buf); return; } if (actual_count > 0) { // Update taint status. The taint status update tries to minimize updates by updating status of contiguous chunk // of bytes with same taint taint_t curr_taint_status = tmp_taint_buf[0]; uint64_t start_offset = 0, curr_offset = 1, slice_size = 1; for (int i = 0; i < actual_count; i++) { if (tmp_taint_buf[i] == TAINT_STATUS_CONCRETE) { // Track address of concrete write by syscall for finding write-write conflicts with other concrete // writes syscall_concrete_writes.emplace(buf + i); } } for (; curr_offset < actual_count; curr_offset++) { if (tmp_taint_buf[curr_offset] != curr_taint_status) { // Taint status of next byte differs. Update all previous ones handle_write(buf + start_offset, slice_size, true, (curr_taint_status == TAINT_SYMBOLIC)); if (stopped) { free(tmp_buf); return; } start_offset = curr_offset; curr_taint_status = tmp_taint_buf[curr_offset]; slice_size = 0; } else { slice_size++; } } if (start_offset != curr_offset) { // Taint status of some more bytes need to be updated handle_write(buf + start_offset, slice_size, true, (curr_taint_status == TAINT_SYMBOLIC)); if (stopped) { free(tmp_buf); return; } } uc_mem_write(uc, buf, tmp_buf, actual_count); } free(tmp_buf); if (rx_bytes != 0) { handle_write(rx_bytes, 4, true); if (stopped) { return; } uc_mem_write(uc, rx_bytes, &actual_count, 4); } count = 0; uc_reg_write(uc, UC_X86_REG_EAX, &count); step(cgc_receive_bbl, 0, false); commit(); if (actual_count > 0) { // Save a block with an instruction to track that the receive syscall needs to be re-executed. The instruction // data is used only to work with existing mechanism to return data to python. // Save all non-symbolic register arguments needed for syscall. block_details_t block_for_receive; block_for_receive.block_addr = cgc_receive_bbl; block_for_receive.block_size = 0; block_for_receive.block_trace_ind = executed_blocks_count; block_for_receive.has_symbolic_exit = false; vex_stmt_details_t stmt_for_receive; // First argument: ebx register_value_t reg_val; if (!is_symbolic_register(20, 4)) { reg_val.offset = 20; reg_val.size = 4; get_register_value(reg_val.offset, reg_val.value); stmt_for_receive.reg_deps.emplace(reg_val); } // Second argument: ecx if (!is_symbolic_register(12, 4)) { reg_val.offset = 12; reg_val.size = 4; get_register_value(reg_val.offset, reg_val.value); stmt_for_receive.reg_deps.emplace(reg_val); } // Third argument: edx if (!is_symbolic_register(16, 4)) { reg_val.offset = 16; reg_val.size = 4; get_register_value(reg_val.offset, reg_val.value); stmt_for_receive.reg_deps.emplace(reg_val); } block_for_receive.symbolic_stmts.emplace_back(stmt_for_receive); blocks_with_symbolic_stmts.emplace_back(block_for_receive); } interrupt_handled = true; syscall_count++; return; } void State::perform_cgc_transmit() { // basically an implementation of the cgc transmit syscall //printf(".. TRANSMIT!\n"); uint32_t fd, buf, count, tx_bytes; uc_err err; uc_reg_read(uc, UC_X86_REG_EBX, &fd); if (fd < 3) { // Process transmits to fd 0, 1 or 2 only. uc_reg_read(uc, UC_X86_REG_ECX, &buf); uc_reg_read(uc, UC_X86_REG_EDX, &count); uc_reg_read(uc, UC_X86_REG_ESI, &tx_bytes); // ensure that the memory we're sending is not tainted // TODO: Can transmit also work with symbolic bytes? void *dup_buf = malloc(count); uint32_t tmp_tx; err = uc_mem_read(uc, buf, dup_buf, count); if (err == UC_ERR_READ_UNMAPPED) { py_mem_callback(uc, UC_MEM_READ_UNMAPPED, buf, count, 0, (void*)0); if (uc_mem_read(uc, buf, dup_buf, count) != UC_ERR_OK) { //printf("... fault on buf\n"); free(dup_buf); return; } } else if (err != UC_ERR_OK) { // Read failed due to some other error. Abort. free(dup_buf); return; } if (!handle_symbolic_syscalls && (find_tainted(buf, count) != -1)) { //printf("... symbolic data\n"); free(dup_buf); return; } step(cgc_transmit_bbl, 0, false); commit(); if (stopped) { //printf("... stopped after step()\n"); free(dup_buf); return; } if (tx_bytes != 0) { handle_write(tx_bytes, 4, true); if (stopped) { return; } uc_mem_write(uc, tx_bytes, &count, 4); } if (stopped) { return; } transmit_records.push_back({fd, dup_buf, count}); int result = 0; uc_reg_write(uc, UC_X86_REG_EAX, &result); symbolic_registers.erase(8); symbolic_registers.erase(9); symbolic_registers.erase(10); symbolic_registers.erase(11); syscall_count++; } interrupt_handled = true; return; } static void hook_mem_read(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { // uc_mem_read(uc, address, &value, size); // //LOG_D("mem_read [%#lx, %#lx] = %#lx", address, address + size); //LOG_D("mem_read [%#lx, %#lx]", address, address + size); State *state = (State *)user_data; state->propagate_taint_of_mem_read_instr_and_continue(address, size); return; } /* * the goal of hooking memory write is to determine the exact * positions of dirty bytes to writing chaneges back to angr * state. However if the hook is hit before mapping requested * page (as writable), we cannot find the bitmap for this page. * In this case, just mark all the position as clean (before * this access). */ static void hook_mem_write(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { //LOG_D("mem_write [%#lx, %#lx]", address, address + size); State *state = (State *)user_data; if (state->ignore_next_selfmod) { // ...the self-modification gets repeated for internal qemu reasons state->ignore_next_selfmod = false; } else if ((address >= state->cur_address && address < state->cur_address + state->cur_size) || // CODE IS SELF-MODIFYING: qemu will restart this basic block at this address. // discard the next block hook (state->cur_address >= address && state->cur_address < address + size)) { state->ignore_next_block = true; } state->handle_write(address, size); } static void hook_block(uc_engine *uc, uint64_t address, int32_t size, void *user_data) { //LOG_I("block [%#lx, %#lx]", address, address + size); State *state = (State *)user_data; if (state->ignore_next_block) { state->ignore_next_block = false; state->ignore_next_selfmod = true; return; } state->commit(); state->set_curr_block_details(address, size); bool hooked = state->step(address, size); if (!hooked && !state->stopped) { state->start_propagating_taint(); } return; } static void hook_intr(uc_engine *uc, uint32_t intno, void *user_data) { State *state = (State *)user_data; state->interrupt_handled = false; auto curr_simos = state->get_simos(); if (curr_simos == SIMOS_CGC) { assert (state->arch == UC_ARCH_X86); assert (state->unicorn_mode == UC_MODE_32); if (intno == 0x80) { if (!state->handle_symbolic_syscalls) { for (auto sr : state->symbolic_registers) { // eax,ecx,edx,ebx,esi if ((sr >= 8 && sr <= 23) || (sr >= 32 && sr <= 35)) return; } } uint32_t sysno; uc_reg_read(uc, UC_X86_REG_EAX, &sysno); //printf("SYSCALL: %d\n", sysno); if ((sysno == state->cgc_transmit_sysno) && (state->cgc_transmit_bbl != 0)) { state->perform_cgc_transmit(); } else if ((sysno == state->cgc_receive_sysno) && (state->cgc_receive_bbl != 0)) { state->perform_cgc_receive(); } else if ((sysno == state->cgc_random_sysno) && (state->cgc_random_bbl != 0)) { state->perform_cgc_random(); } } } } static bool hook_mem_unmapped(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { State *state = (State *)user_data; uint64_t start = address & ~0xFFFULL; uint64_t end = (address + size - 1) & ~0xFFFULL; // only hook nonwritable pages if (type != UC_MEM_WRITE_UNMAPPED && state->map_cache(start, 0x1000) && (start == end || state->map_cache(end, 0x1000))) { //LOG_D("handle unmapped page natively"); return true; } return false; } static bool hook_mem_prot(uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, void *user_data) { State *state = (State *)user_data; //printf("Segfault data: %d %#llx %d %#llx\n", type, address, size, value); state->stop(STOP_SEGFAULT); return false; } /* * C style bindings makes it simple and dirty */ extern "C" State *simunicorn_alloc(uc_engine *uc, uint64_t cache_key, simos_t simos, bool handle_symbolic_addrs, bool handle_symb_cond, bool handle_symb_syscalls) { State *state = new State(uc, cache_key, simos, handle_symbolic_addrs, handle_symb_cond, handle_symb_syscalls); return state; } extern "C" void simunicorn_dealloc(State *state) { delete state; } extern "C" uint64_t *simunicorn_bbl_addrs(State *state) { return &(state->bbl_addrs[0]); } extern "C" uint64_t *simunicorn_stack_pointers(State *state) { return &(state->stack_pointers[0]); } extern "C" uint64_t simunicorn_bbl_addr_count(State *state) { return state->bbl_addrs.size(); } extern "C" uint64_t simunicorn_syscall_count(State *state) { return state->syscall_count; } extern "C" void simunicorn_hook(State *state) { state->hook(); } extern "C" void simunicorn_unhook(State *state) { state->unhook(); } extern "C" uc_err simunicorn_start(State *state, uint64_t pc, uint64_t step) { return state->start(pc, step); } extern "C" void simunicorn_stop(State *state, stop_t reason) { state->stop(reason); } extern "C" mem_update_t *simunicorn_sync(State *state) { return state->sync(); } extern "C" uint64_t simunicorn_step(State *state) { return state->cur_steps; } extern "C" void simunicorn_set_last_block_details(State *state, address_t block_addr, uint64_t curr_count, uint64_t total_count) { state->set_last_block_details(block_addr, curr_count, total_count); } extern "C" void simunicorn_set_random_syscall_data(State *state, uint64_t *values, uint64_t *sizes, uint64_t count) { state->init_random_bytes(values, sizes, count); } extern "C" void simunicorn_set_stops(State *state, uint64_t count, uint64_t *stops) { state->set_stops(count, stops); } extern "C" void simunicorn_activate_page(State *state, uint64_t address, uint8_t *taint, uint8_t *data) { state->page_activate(address, taint, data); } extern "C" uint64_t simunicorn_executed_pages(State *state) { // this is HORRIBLE if (state->executed_pages_iterator == NULL) { state->executed_pages_iterator = new std::unordered_set::iterator; *state->executed_pages_iterator = state->executed_pages.begin(); } if (*state->executed_pages_iterator == state->executed_pages.end()) { delete state->executed_pages_iterator; state->executed_pages_iterator = NULL; return -1; } uint64_t out = **state->executed_pages_iterator; (*state->executed_pages_iterator)++; return out; } // // Stop analysis // extern "C" stop_details_t simunicorn_get_stop_details(State *state) { return state->stop_details; } // // Symbolic register tracking // extern "C" void simunicorn_symbolic_register_data(State *state, uint64_t count, uint64_t *offsets) { state->symbolic_registers.clear(); for (auto i = 0; i < count; i++) { state->symbolic_registers.insert(offsets[i]); } } extern "C" uint64_t simunicorn_get_symbolic_registers(State *state, uint64_t *output) { int i = 0; for (auto r : state->symbolic_registers) { output[i] = r; i++; } return i; } extern "C" void simunicorn_enable_symbolic_reg_tracking(State *state, VexArch guest, VexArchInfo archinfo) { state->vex_guest = guest; state->vex_archinfo = archinfo; } extern "C" void simunicorn_disable_symbolic_reg_tracking(State *state) { state->vex_guest = VexArch_INVALID; } // // Concrete transmits // extern "C" bool simunicorn_is_interrupt_handled(State *state) { return state->interrupt_handled; } extern "C" void simunicorn_set_cgc_syscall_details(State *state, uint32_t transmit_num, uint64_t transmit_bbl, uint32_t receive_num, uint64_t receive_bbl, uint64_t receive_size, uint32_t random_num, uint64_t random_bbl) { state->cgc_random_sysno = random_num; state->cgc_random_bbl = random_bbl; state->cgc_receive_sysno = receive_num; state->cgc_receive_bbl = receive_bbl; state->cgc_receive_max_size = receive_size; state->cgc_transmit_sysno = transmit_num; state->cgc_transmit_bbl = transmit_bbl; } extern "C" bool simunicorn_set_ucproc(State *state, uint64_t hook_addr, char *proc_name) { void (*func)(State *) = nullptr; std::string name(proc_name); if (name == "malloc") { func = ucproc_malloc; } else if (name == "memset") { func = ucproc_memset; } else { return false; } state->uc_procedures[hook_addr] = func; return true; } extern "C" uint64_t simunicorn_get_heap_base(State *state) { return state->heap_base; } extern "C" void simunicorn_set_heap_base(State *state, uint64_t base) { state->heap_base = base; } extern "C" transmit_record_t *simunicorn_process_transmit(State *state, uint32_t num) { if (num >= state->transmit_records.size()) { for (auto record_iter = state->transmit_records.begin(); record_iter != state->transmit_records.end(); record_iter++) { free(record_iter->data); } state->transmit_records.clear(); return NULL; } else { transmit_record_t *out = &state->transmit_records[num]; return out; } } /* * Set concrete bytes of an open file for use in tracing */ extern "C" void simunicorn_set_fd_bytes(State *state, uint64_t fd, char *input, taint_t *taints, uint64_t len, uint64_t read_pos) { state->fd_init_bytes(fd, input, taints, len, read_pos); return; } /* * Page cache */ extern "C" bool simunicorn_cache_page(State *state, uint64_t address, uint64_t length, char *bytes, uint64_t permissions) { //LOG_I("caching [%#lx, %#lx]", address, address + length); auto actual = state->cache_page(address, length, bytes, permissions); if (!state->map_cache(actual.first, actual.second)) { return false; } return true; } extern "C" void simunicorn_uncache_pages_touching_region(State *state, uint64_t address, uint64_t length) { state->uncache_pages_touching_region(address, length); } extern "C" void simunicorn_clear_page_cache(State *state) { state->clear_page_cache(); } // Tracking settings extern "C" void simunicorn_set_tracking(State *state, bool track_bbls, bool track_stack) { state->track_bbls = track_bbls; state->track_stack = track_stack; } extern "C" bool simunicorn_in_cache(State *state, uint64_t address) { return state->in_cache(address); } extern "C" void simunicorn_set_map_callback(State *state, uc_cb_eventmem_t cb) { state->py_mem_callback = cb; } // VEX artificial registers list extern "C" void simunicorn_set_artificial_registers(State *state, uint64_t *offsets, uint64_t count) { state->artificial_vex_registers.clear(); for (auto i = 0; i < count; i++) { state->artificial_vex_registers.emplace(offsets[i]); } return; } // VEX register offsets to unicorn register ID mappings extern "C" void simunicorn_set_vex_to_unicorn_reg_mappings(State *state, uint64_t *vex_offsets, uint64_t *unicorn_ids, uint64_t *reg_sizes, uint64_t count) { state->vex_to_unicorn_map.clear(); for (auto i = 0; i < count; i++) { state->vex_to_unicorn_map.emplace(vex_offsets[i], std::make_pair(unicorn_ids[i], reg_sizes[i])); } return; } // Mapping details for flags registers extern "C" void simunicorn_set_cpu_flags_details(State *state, uint64_t *flag_vex_id, uint64_t *uc_reg_id, uint64_t *bitmasks, uint64_t count) { state->cpu_flags.clear(); for (auto i = 0; i < count; i++) { state->cpu_flags.emplace(flag_vex_id[i], std::make_pair(uc_reg_id[i], bitmasks[i])); } return; } extern "C" void simunicorn_set_register_blacklist(State *state, uint64_t *reg_list, uint64_t count) { state->blacklisted_registers.clear(); for (auto i = 0; i < count; i++) { state->blacklisted_registers.emplace(reg_list[i]); } return; } extern "C" void simunicorn_set_vex_cc_reg_data(State *state, uint64_t *reg_offsets, uint64_t *reg_sizes, uint64_t count) { state->vex_cc_regs.clear(); for (auto i = 0; i < count; i++) { state->vex_cc_regs.emplace(reg_offsets[i], reg_sizes[i]); } return; } // VEX re-execution data extern "C" uint64_t simunicorn_get_count_of_blocks_with_symbolic_vex_stmts(State *state) { return state->block_details_to_return.size(); } extern "C" void simunicorn_get_details_of_blocks_with_symbolic_vex_stmts(State *state, sym_block_details_ret_t *ret_block_details) { for (auto i = 0; i < state->block_details_to_return.size(); i++) { ret_block_details[i].block_addr = state->block_details_to_return[i].block_addr; ret_block_details[i].block_size = state->block_details_to_return[i].block_size; ret_block_details[i].block_trace_ind = state->block_details_to_return[i].block_trace_ind; ret_block_details[i].has_symbolic_exit = state->block_details_to_return[i].has_symbolic_exit; ret_block_details[i].symbolic_stmts = &(state->block_details_to_return[i].symbolic_stmts[0]); ret_block_details[i].symbolic_stmts_count = state->block_details_to_return[i].symbolic_stmts.size(); ret_block_details[i].register_values = &(state->block_details_to_return[i].register_values[0]); ret_block_details[i].register_values_count = state->block_details_to_return[i].register_values.size(); } return; } // Concrete writes to re-execute extern "C" uint64_t simunicorn_get_count_of_writes_to_reexecute(State *state) { return state->concrete_writes_to_reexecute.size(); } extern "C" void simunicorn_get_concrete_writes_to_reexecute(State *state, uint64_t *addrs, uint8_t *values) { uint64_t count = 0; for (auto &entry: state->concrete_writes_to_reexecute) { addrs[count] = entry.first; values[count] = entry.second; count++; } return; } extern "C" void simunicorn_set_fp_regs_fp_ops_vex_codes(State *state, uint64_t start_offset, uint64_t size, uint64_t *ops, uint32_t op_count) { state->fp_reg_vex_data.first = start_offset; state->fp_reg_vex_data.second = size; for (auto i = 0; i < op_count; i++) { state->fp_ops_to_avoid.emplace(ops[i]); } }