mirror of
https://github.com/angr/angr
synced 2026-06-08 13:09:39 +00:00
3263 lines
118 KiB
C++
3263 lines
118 KiB
C++
#define __STDC_FORMAT_MACROS 1
|
|
#define NOMINMAX
|
|
#ifdef _MSC_VER
|
|
// get this out of the way... it's requried deep within unicorn
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cinttypes>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <queue>
|
|
#include <memory>
|
|
#include <map>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <unordered_set>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
#include <libvex.h>
|
|
#include <pyvex.h>
|
|
#include <unicorn/unicorn.h>
|
|
}
|
|
|
|
|
|
#include "sim_unicorn.hpp"
|
|
//#include "log.h"
|
|
|
|
std::map<uint64_t, caches_t> 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<vex_stmt_details_t> sym_stmts;
|
|
std::unordered_set<register_value_t> 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);
|
|
taint_t *bitmap = page.first;
|
|
|
|
uint64_t start = rit->address & 0xFFF;
|
|
int size = rit->size;
|
|
for (auto i = 0; i < size; i++) {
|
|
bitmap[start + i] = rit->previous_taint[i];
|
|
}
|
|
}
|
|
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 PageBitmap only if the page is remapped for writing,
|
|
* or initialized with symbolic variable, otherwise return NULL.
|
|
*/
|
|
std::pair<taint_t *, uint8_t *> State::page_lookup(address_t address) const {
|
|
address &= ~0xFFFULL;
|
|
auto it = active_pages.find(address);
|
|
if (it == active_pages.end()) {
|
|
return std::pair<taint_t *, uint8_t *>(NULL, NULL);
|
|
}
|
|
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()) {
|
|
if (data == NULL) {
|
|
// We need to copy the taint bitmap
|
|
taint_t *bitmap = new PageBitmap;
|
|
memcpy(bitmap, taint, sizeof(PageBitmap));
|
|
|
|
active_pages.insert(std::pair<address_t, std::pair<taint_t*, uint8_t*>>(address, std::pair<taint_t*, uint8_t*>(bitmap, NULL)));
|
|
} else {
|
|
// We can directly use the passed taint and data
|
|
taint_t *bitmap = (taint_t*)taint;
|
|
active_pages.insert(std::pair<uint64_t, std::pair<taint_t*, uint8_t*>>(address, std::pair<taint_t*, uint8_t*>(bitmap, data)));
|
|
}
|
|
} 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 *data = it->second.second;
|
|
if (data != NULL) {
|
|
// nothing to sync, direct mapped :)
|
|
continue;
|
|
}
|
|
taint_t *start = it->second.first;
|
|
taint_t *end = &it->second.first[0x1000];
|
|
//LOG_D("found active page %#lx (%p)", it->first, start);
|
|
for (taint_t *i = start; i < end; i++)
|
|
if ((*i) == TAINT_DIRTY) {
|
|
taint_t *j = i;
|
|
while (j < end && (*j) == TAINT_DIRTY) j++;
|
|
|
|
char buf[0x1000];
|
|
uc_mem_read(uc, it->first + (i - start), buf, j - i);
|
|
//LOG_D("sync [%#lx, %#lx] = %#lx", it->first + (i - start), it->first + (j - start), *(uint64_t *)buf);
|
|
|
|
mem_update_t *range = new mem_update_t;
|
|
range->address = it->first + (i - start);
|
|
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<address_t, size_t> 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_t, CachedPage>(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) {
|
|
taint_t *bitmap = page_lookup(address).first;
|
|
|
|
int start = address & 0xFFF;
|
|
int end = (address + size - 1) & 0xFFF;
|
|
|
|
if (end >= start) {
|
|
if (bitmap) {
|
|
for (auto i = start; i <= end; i++) {
|
|
if (bitmap[i] & TAINT_SYMBOLIC) {
|
|
return (address & ~0xFFF) + i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// cross page boundary
|
|
if (bitmap) {
|
|
for (auto i = start; i <= 0xFFF; i++) {
|
|
if (bitmap[i] & TAINT_SYMBOLIC) {
|
|
return (address & ~0xFFF) + i;
|
|
}
|
|
}
|
|
}
|
|
|
|
bitmap = page_lookup(address + size - 1).first;
|
|
if (bitmap) {
|
|
for (auto i = 0; i <= end; i++) {
|
|
if (bitmap[i] & TAINT_SYMBOLIC) {
|
|
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 pair = page_lookup(address);
|
|
taint_t *bitmap = pair.first;
|
|
uint8_t *data = pair.second;
|
|
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<uint32_t> 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++) {
|
|
record.previous_taint.push_back(bitmap[i]);
|
|
if (is_dst_symbolic) {
|
|
// Don't mark 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[i] = TAINT_SYMBOLIC;
|
|
}
|
|
else if (bitmap[i] != TAINT_DIRTY) {
|
|
bitmap[i] = TAINT_DIRTY;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (auto i = start; i <= end; i++) {
|
|
record.previous_taint.push_back(bitmap[i]);
|
|
if (is_dst_symbolic) {
|
|
// Don't mark 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[i] = TAINT_SYMBOLIC;
|
|
}
|
|
else if (bitmap[i] != TAINT_NONE) {
|
|
bitmap[i] = TAINT_NONE;
|
|
}
|
|
}
|
|
}
|
|
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<int64_t> 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<taint_entity_t, int> last_entity_setter;
|
|
std::unordered_set<vex_reg_offset_t> 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<vex_stmt_details_t> State::get_list_of_dep_stmts(const vex_stmt_details_t &stmt) const {
|
|
std::set<vex_stmt_details_t> 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<taint_entity_t, int> &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_entity_t> &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_entity_t> &taint_sources) const {
|
|
std::unordered_set<taint_entity_t> 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<uint8_t[]> 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<uint8_t[]> 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<uint8_t[]> 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<int64_t, vex_stmt_taint_entry_t>::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<vex_reg_offset_t, int64_t> 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_value_t> 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<int64_t>({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<int64_t>({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<int64_t>({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<std::set<vex_stmt_details_t>::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<uint8_t> 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<address_t>::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]);
|
|
}
|
|
}
|