1
0
mirror of https://github.com/angr/angr synced 2026-06-08 13:09:39 +00:00
Files

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 &reg_offset: block_symbolic_registers) {
symbolic_registers.emplace(reg_offset);
}
for (auto &reg_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, &reg_value);
}
else {
uc_reg_read(uc, cpu_flags_entry->second.first, &reg_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 *)&reg_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]);
}
}