diff --git a/src/analyzer/main.cpp b/src/analyzer/main.cpp index 9d4bd0ee..9e1e4125 100644 --- a/src/analyzer/main.cpp +++ b/src/analyzer/main.cpp @@ -8,12 +8,11 @@ #include "object_watching.hpp" #include "snapshot.hpp" #include "analysis.hpp" +#include "tenet_tracer.hpp" #include #include -#include - #ifdef OS_EMSCRIPTEN #include #endif @@ -24,6 +23,7 @@ namespace { mutable bool use_gdb{false}; bool log_executable_access{false}; + bool tenet_trace{false}; std::filesystem::path dump{}; std::filesystem::path minidump_path{}; std::string registry_path{"./registry"}; @@ -296,6 +296,13 @@ namespace win_emu->log.log("Using emulator: %s\n", win_emu->emu().get_name().c_str()); + std::optional tenet_tracer{}; + if (options.tenet_trace) + { + win_emu->log.log("Tenet Tracer enabled. Output: tenet_trace.log\n"); + tenet_tracer.emplace(*win_emu, "tenet_trace.log"); + } + register_analysis_callbacks(context); watch_system_objects(*win_emu, options.modules, options.verbose_logging); @@ -405,6 +412,7 @@ namespace printf(" -e, --emulation Set emulation root path\n"); printf(" -a, --snapshot Load snapshot dump from path\n"); printf(" --minidump Load minidump from path\n"); + printf(" -t, --tenet-trace Enable Tenet tracer\n"); printf(" -i, --ignore Comma-separated list of functions to ignore\n"); printf(" -p, --path Map Windows path to host path\n"); printf(" -r, --registry Set registry path (default: ./registry)\n\n"); @@ -451,6 +459,10 @@ namespace { options.concise_logging = true; } + else if (arg == "-t" || arg == "--tenet-trace") + { + options.tenet_trace = true; + } else if (arg == "-m" || arg == "--module") { if (args.size() < 2) diff --git a/src/analyzer/std_include.hpp b/src/analyzer/std_include.hpp index 9719ff6b..f88970da 100644 --- a/src/analyzer/std_include.hpp +++ b/src/analyzer/std_include.hpp @@ -6,15 +6,16 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include +#include #include #include diff --git a/src/analyzer/tenet_tracer.cpp b/src/analyzer/tenet_tracer.cpp new file mode 100644 index 00000000..26d3c9b1 --- /dev/null +++ b/src/analyzer/tenet_tracer.cpp @@ -0,0 +1,269 @@ +#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->is_within(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(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(); +} \ No newline at end of file diff --git a/src/analyzer/tenet_tracer.hpp b/src/analyzer/tenet_tracer.hpp new file mode 100644 index 00000000..de0408f0 --- /dev/null +++ b/src/analyzer/tenet_tracer.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +constexpr std::array, 16> GPRs_TO_TRACE = { + { + {x86_register::rax, "rax"}, + {x86_register::rbx, "rbx"}, + {x86_register::rcx, "rcx"}, + {x86_register::rdx, "rdx"}, + {x86_register::rsi, "rsi"}, + {x86_register::rdi, "rdi"}, + {x86_register::rbp, "rbp"}, + {x86_register::rsp, "rsp"}, + {x86_register::r8, "r8"}, + {x86_register::r9, "r9"}, + {x86_register::r10, "r10"}, + {x86_register::r11, "r11"}, + {x86_register::r12, "r12"}, + {x86_register::r13, "r13"}, + {x86_register::r14, "r14"}, + {x86_register::r15, "r15"}, + }, +}; + +class tenet_tracer +{ + public: + tenet_tracer(windows_emulator& win_emu, const std::filesystem::path& log_filename); + ~tenet_tracer(); + + tenet_tracer(tenet_tracer&) = delete; + tenet_tracer(const tenet_tracer&) = delete; + tenet_tracer& operator=(tenet_tracer&) = delete; + tenet_tracer& operator=(const tenet_tracer&) = delete; + + private: + void filter_and_write_buffer(); + void log_memory_read(uint64_t address, const void* data, size_t size); + void log_memory_write(uint64_t address, const void* data, size_t size); + void process_instruction(uint64_t address); + + windows_emulator& win_emu_; + std::ofstream log_file_; + + std::vector raw_log_buffer_; + std::array previous_registers_{}; + bool is_first_instruction_ = true; + + std::stringstream mem_read_log_; + std::stringstream mem_write_log_; + + scoped_hook read_hook_; + scoped_hook write_hook_; + scoped_hook execute_hook_; +};