#include "std_include.hpp" #include "tenet_tracer.hpp" #include #include namespace { std::string format_hex(uint64_t value) { std::stringstream ss; ss << "0x" << std::hex << value; return ss.str(); } std::string format_byte_array(const uint8_t* data, size_t size) { std::stringstream ss; for (size_t i = 0; i < size; ++i) { ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(data[i]); } return ss.str(); } void parse_and_accumulate_changes(const std::string& line, std::map& changes) { size_t start = 0; while (start < line.length()) { size_t end = line.find(',', start); if (end == std::string::npos) { end = line.length(); } std::string pair_str = line.substr(start, end - start); size_t equals_pos = pair_str.find('='); if (equals_pos != std::string::npos) { std::string key = pair_str.substr(0, equals_pos); std::string value = pair_str.substr(equals_pos + 1); changes[key] = value; } start = end + 1; } } } tenet_tracer::tenet_tracer(windows_emulator& win_emu, const std::filesystem::path& log_filename) : win_emu_(win_emu), log_file_(log_filename) { if (!log_file_) { throw std::runtime_error("TenetTracer: Failed to open log file -> " + log_filename.string()); } auto& emu = win_emu_.emu(); auto* read_hook = emu.hook_memory_read(0, 0xFFFFFFFFFFFFFFFF, [this](uint64_t a, const void* d, size_t s) { this->log_memory_read(a, d, s); // }); read_hook_ = scoped_hook(emu, read_hook); auto* write_hook = emu.hook_memory_write(0, 0xFFFFFFFFFFFFFFFF, [this](uint64_t a, const void* d, size_t s) { this->log_memory_write(a, d, s); // }); write_hook_ = scoped_hook(emu, write_hook); auto* execute_hook = emu.hook_memory_execution([&](uint64_t address) { this->process_instruction(address); // }); execute_hook_ = scoped_hook(emu, execute_hook); } tenet_tracer::~tenet_tracer() { filter_and_write_buffer(); if (log_file_.is_open()) { log_file_.close(); } } void tenet_tracer::filter_and_write_buffer() { if (raw_log_buffer_.empty()) { return; } const auto* exe_module = win_emu_.mod_manager.executable; if (!exe_module) { for (const auto& line : raw_log_buffer_) { log_file_ << line << '\n'; } return; } if (!raw_log_buffer_.empty()) { log_file_ << raw_log_buffer_.front() << '\n'; } bool currently_outside = false; std::map accumulated_changes; for (size_t i = 1; i < raw_log_buffer_.size(); ++i) { const auto& line = raw_log_buffer_[i]; size_t rip_pos = line.find("rip=0x"); if (rip_pos == std::string::npos) { continue; } char* end_ptr = nullptr; uint64_t address = std::strtoull(line.c_str() + rip_pos + 6, &end_ptr, 16); bool is_line_inside = exe_module->contains(address); const auto _1 = utils::finally([&] { currently_outside = !is_line_inside; // }); if (!is_line_inside) { parse_and_accumulate_changes(line, accumulated_changes); continue; } const auto _2 = utils::finally([&] { log_file_ << line << '\n'; // }); if (!currently_outside || accumulated_changes.empty()) { continue; } std::stringstream summary_line; bool first = true; auto rip_it = accumulated_changes.find("rip"); std::string last_rip; if (rip_it != accumulated_changes.end()) { last_rip = rip_it->second; accumulated_changes.erase(rip_it); } for (const auto& pair : accumulated_changes) { if (!first) { summary_line << ","; } summary_line << pair.first << "=" << pair.second; first = false; } if (!last_rip.empty()) { if (!first) { summary_line << ","; } summary_line << "rip=" << last_rip; } log_file_ << summary_line.str() << '\n'; accumulated_changes.clear(); } raw_log_buffer_.clear(); } void tenet_tracer::log_memory_read(uint64_t address, const void* data, size_t size) { if (!mem_read_log_.str().empty()) { mem_read_log_ << ";"; } mem_read_log_ << format_hex(address) << ":" << format_byte_array(static_cast(data), size); } void tenet_tracer::log_memory_write(uint64_t address, const void* data, size_t size) { if (!mem_write_log_.str().empty()) { mem_write_log_ << ";"; } mem_write_log_ << format_hex(address) << ":" << format_byte_array(static_cast(data), size); } void tenet_tracer::process_instruction(const uint64_t address) { auto& emu = win_emu_.emu(); std::stringstream trace_line; std::array current_regs{}; for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i) { current_regs[i] = emu.reg(GPRs_TO_TRACE[i].first); } bool first_entry = true; auto append_separator = [&] { if (!first_entry) { trace_line << ","; } first_entry = false; }; if (is_first_instruction_) { for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i) { append_separator(); trace_line << GPRs_TO_TRACE[i].second << "=" << format_hex(current_regs[i]); } is_first_instruction_ = false; } else { for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i) { if (previous_registers_[i] != current_regs[i]) { append_separator(); trace_line << GPRs_TO_TRACE[i].second << "=" << format_hex(current_regs[i]); } } } append_separator(); trace_line << "rip=" << format_hex(address); const auto mem_reads = mem_read_log_.str(); if (!mem_reads.empty()) { append_separator(); trace_line << "mr=" << mem_reads; } const auto mem_writes = mem_write_log_.str(); if (!mem_writes.empty()) { append_separator(); trace_line << "mw=" << mem_writes; } raw_log_buffer_.push_back(trace_line.str()); previous_registers_ = current_regs; mem_read_log_.str(""); mem_read_log_.clear(); mem_write_log_.str(""); mem_write_log_.clear(); }