Generate an output that the Tenet plugin can read

Generates a Tenet tracer output with the -t parameter.
The original Tenet tracer plugin is outdated, so I made updates to support our emulator. The forked project below works well.

https://github.com/maskelihileci/tenet/tree/master/plugins_sogen-support
This commit is contained in:
maskelihileci
2025-07-17 14:36:29 +03:00
committed by GitHub
parent b34adbadbf
commit 20f96841de
12 changed files with 3683 additions and 3340 deletions

View File

@@ -1,39 +1,39 @@
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
*.cpp
*.hpp
*.rc
)
list(SORT SRC_FILES)
add_executable(analyzer ${SRC_FILES})
momo_assign_source_group(${SRC_FILES})
if(NOT MOMO_ENABLE_CLANG_TIDY)
target_precompile_headers(analyzer PRIVATE std_include.hpp)
endif()
target_link_libraries(analyzer PRIVATE
reflect
debugger
windows-emulator
windows-gdb-stub
backend-selection
)
set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT analyzer)
momo_strip_target(analyzer)
set(ENV_PREFIX "$")
set(ENV_SUFFIX "")
if(WIN)
set(ENV_PREFIX "%")
set(ENV_SUFFIX "%")
endif()
add_test(NAME analyzer-test
COMMAND "${PYTHON3_EXE}" "${CMAKE_CURRENT_LIST_DIR}/test.py"
WORKING_DIRECTORY "$<TARGET_FILE_DIR:analyzer>")
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
*.cpp
*.hpp
*.rc
)
list(SORT SRC_FILES)
add_executable(analyzer ${SRC_FILES})
momo_assign_source_group(${SRC_FILES})
if(NOT MOMO_ENABLE_CLANG_TIDY)
target_precompile_headers(analyzer PRIVATE std_include.hpp)
endif()
target_link_libraries(analyzer PRIVATE
reflect
debugger
windows-emulator
windows-gdb-stub
backend-selection
)
set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT analyzer)
momo_strip_target(analyzer)
set(ENV_PREFIX "$")
set(ENV_SUFFIX "")
if(WIN)
set(ENV_PREFIX "%")
set(ENV_SUFFIX "%")
endif()
add_test(NAME analyzer-test
COMMAND "${PYTHON3_EXE}" "${CMAKE_CURRENT_LIST_DIR}/test.py"
WORKING_DIRECTORY "$<TARGET_FILE_DIR:analyzer>")

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,43 @@
#pragma once
#include "reflect_type_info.hpp"
#include <set>
#include <cinttypes>
template <typename T>
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules,
emulator_object<T> object, const auto verbose)
{
const reflect_type_info<T> info{};
return emu.emu().hook_memory_read(
object.value(), static_cast<size_t>(object.size()),
[i = std::move(info), object, &emu, verbose, modules](const uint64_t address, const void*, size_t) {
const auto rip = emu.emu().read_instruction_pointer();
const auto* mod = emu.mod_manager.find_by_address(rip);
const auto is_main_access = mod == emu.mod_manager.executable || modules.contains(mod->name);
if (!verbose && !is_main_access)
{
return;
}
if (!verbose)
{
static std::unordered_set<uint64_t> logged_addresses{};
if (is_main_access && !logged_addresses.insert(address).second)
{
return;
}
}
const auto offset = address - object.value();
const auto* mod_name = mod ? mod->name.c_str() : "<N/A>";
const auto& type_name = i.get_type_name();
const auto member_name = i.get_member_name(static_cast<size_t>(offset));
emu.log.print(is_main_access ? color::green : color::dark_gray,
"Object access: %s - 0x%" PRIx64 " (%s) at 0x%" PRIx64 " (%s)\n", type_name.c_str(), offset,
member_name.c_str(), rip, mod_name);
});
}
#pragma once
#include "reflect_type_info.hpp"
#include <set>
#include <cinttypes>
template <typename T>
emulator_hook* watch_object(windows_emulator& emu, const std::set<std::string, std::less<>>& modules,
emulator_object<T> object, const auto verbose)
{
const reflect_type_info<T> info{};
return emu.emu().hook_memory_read(
object.value(), static_cast<size_t>(object.size()),
[i = std::move(info), object, &emu, verbose, modules](const uint64_t address, const void*, size_t) {
const auto rip = emu.emu().read_instruction_pointer();
const auto* mod = emu.mod_manager.find_by_address(rip);
const auto is_main_access = mod == emu.mod_manager.executable || modules.contains(mod->name);
if (!verbose && !is_main_access)
{
return;
}
if (!verbose)
{
static std::unordered_set<uint64_t> logged_addresses{};
if (is_main_access && !logged_addresses.insert(address).second)
{
return;
}
}
const auto offset = address - object.value();
const auto* mod_name = mod ? mod->name.c_str() : "<N/A>";
const auto& type_name = i.get_type_name();
const auto member_name = i.get_member_name(static_cast<size_t>(offset));
emu.log.print(is_main_access ? color::green : color::dark_gray,
"Object access: %s - 0x%" PRIx64 " (%s) at 0x%" PRIx64 " (%s)\n", type_name.c_str(), offset,
member_name.c_str(), rip, mod_name);
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,83 @@
#pragma once
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wtautological-compare"
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"
#endif
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4308)
#endif
#include "reflect_extension.hpp"
#include <reflect>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
template <typename T>
class reflect_type_info
{
public:
reflect_type_info()
{
this->type_name_ = reflect::type_name<T>();
reflect::for_each<T>([this](auto I) {
const auto member_name = reflect::member_name<I, T>();
const auto member_offset = reflect::offset_of<I, T>();
this->members_[member_offset] = member_name;
});
}
std::string get_member_name(const size_t offset) const
{
size_t last_offset{};
std::string_view last_member{};
for (const auto& member : this->members_)
{
if (offset == member.first)
{
return member.second;
}
if (offset < member.first)
{
const auto diff = offset - last_offset;
return std::string(last_member) + "+" + std::to_string(diff);
}
last_offset = member.first;
last_member = member.second;
}
return "<N/A>";
}
const std::string& get_type_name() const
{
return this->type_name_;
}
private:
std::string type_name_{};
std::map<size_t, std::string> members_{};
};
#pragma once
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wtautological-compare"
#endif
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"
#endif
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4308)
#endif
#include "reflect_extension.hpp"
#include <reflect>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
template <typename T>
class reflect_type_info
{
public:
reflect_type_info()
{
this->type_name_ = reflect::type_name<T>();
reflect::for_each<T>([this](auto I) {
const auto member_name = reflect::member_name<I, T>();
const auto member_offset = reflect::offset_of<I, T>();
this->members_[member_offset] = member_name;
});
}
std::string get_member_name(const size_t offset) const
{
size_t last_offset{};
std::string_view last_member{};
for (const auto& member : this->members_)
{
if (offset == member.first)
{
return member.second;
}
if (offset < member.first)
{
const auto diff = offset - last_offset;
return std::string(last_member) + "+" + std::to_string(diff);
}
last_offset = member.first;
last_member = member.second;
}
return "<N/A>";
}
const std::string& get_type_name() const
{
return this->type_name_;
}
private:
std::string type_name_{};
std::map<size_t, std::string> members_{};
};

View File

@@ -1,101 +1,101 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "windows.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"#include ""windows.h""\r\n"
"\0"
END
2 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE VFT_DLL
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "momo5502"
VALUE "FileDescription", "Windows Emulator"
VALUE "FileVersion", "1.0.0.0"
VALUE "InternalName", "emulator"
VALUE "LegalCopyright", "All rights reserved."
VALUE "OriginalFilename", "emulator.exe"
VALUE "ProductName", "emulator"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Binary Data
//
GLFW_ICON ICON "resources/icon.ico"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "windows.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"#include ""windows.h""\r\n"
"\0"
END
2 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE VFT_DLL
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "momo5502"
VALUE "FileDescription", "Windows Emulator"
VALUE "FileVersion", "1.0.0.0"
VALUE "InternalName", "emulator"
VALUE "LegalCopyright", "All rights reserved."
VALUE "OriginalFilename", "emulator.exe"
VALUE "ProductName", "emulator"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Binary Data
//
GLFW_ICON ICON "resources/icon.ico"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View File

@@ -1,123 +1,123 @@
#include "snapshot.hpp"
#include <utils/io.hpp>
#include <utils/compression.hpp>
namespace snapshot
{
namespace
{
struct snapshot_header
{
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
char magic[4] = {'S', 'N', 'A', 'P'};
uint32_t version{1};
};
static_assert(sizeof(snapshot_header) == 8);
std::span<const std::byte> validate_header(const std::span<const std::byte> snapshot)
{
snapshot_header header{};
constexpr snapshot_header default_header{};
if (snapshot.size() < sizeof(header))
{
throw std::runtime_error("Snapshot is too small");
}
memcpy(&header, snapshot.data(), sizeof(header));
if (memcmp(default_header.magic, header.magic, sizeof(header.magic)) != 0)
{
throw std::runtime_error("Invalid snapshot");
}
if (default_header.version != header.version)
{
throw std::runtime_error("Unsupported snapshot version: " + std::to_string(header.version) +
"(needed: " + std::to_string(default_header.version) + ")");
}
return snapshot.subspan(sizeof(header));
}
std::vector<std::byte> get_compressed_emulator_state(const windows_emulator& win_emu)
{
utils::buffer_serializer serializer{};
win_emu.serialize(serializer);
return utils::compression::zlib::compress(serializer.get_buffer());
}
std::vector<std::byte> get_decompressed_emulator_state(const std::span<const std::byte> snapshot)
{
const auto data = validate_header(snapshot);
return utils::compression::zlib::decompress(data);
}
std::string get_main_executable_name(const windows_emulator& win_emu)
{
const auto* exe = win_emu.mod_manager.executable;
if (exe)
{
return std::filesystem::path(exe->name).stem().string();
}
return "process";
}
}
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu)
{
const auto state = get_compressed_emulator_state(win_emu);
snapshot_header header{};
std::span header_span(reinterpret_cast<const std::byte*>(&header), sizeof(header));
std::vector<std::byte> snapshot{};
snapshot.reserve(header_span.size() + state.size());
snapshot.assign(header_span.begin(), header_span.end());
snapshot.insert(snapshot.end(), state.begin(), state.end());
return snapshot;
}
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, const bool log)
{
std::filesystem::path snapshot_file =
get_main_executable_name(win_emu) + "-" + std::to_string(time(nullptr)) + ".snap";
if (log)
{
win_emu.log.log("Writing snapshot to %s...\n", snapshot_file.string().c_str());
}
const auto snapshot = create_emulator_snapshot(win_emu);
if (!utils::io::write_file(snapshot_file, snapshot))
{
throw std::runtime_error("Failed to write snapshot!");
}
return snapshot_file;
}
void load_emulator_snapshot(windows_emulator& win_emu, const std::span<const std::byte> snapshot)
{
const auto data = get_decompressed_emulator_state(snapshot);
utils::buffer_deserializer deserializer{data};
win_emu.deserialize(deserializer);
}
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file)
{
std::vector<std::byte> data{};
if (!utils::io::read_file(snapshot_file, &data))
{
throw std::runtime_error("Failed to read snapshot file: " + snapshot_file.string());
}
load_emulator_snapshot(win_emu, data);
}
}
#include "snapshot.hpp"
#include <utils/io.hpp>
#include <utils/compression.hpp>
namespace snapshot
{
namespace
{
struct snapshot_header
{
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
char magic[4] = {'S', 'N', 'A', 'P'};
uint32_t version{1};
};
static_assert(sizeof(snapshot_header) == 8);
std::span<const std::byte> validate_header(const std::span<const std::byte> snapshot)
{
snapshot_header header{};
constexpr snapshot_header default_header{};
if (snapshot.size() < sizeof(header))
{
throw std::runtime_error("Snapshot is too small");
}
memcpy(&header, snapshot.data(), sizeof(header));
if (memcmp(default_header.magic, header.magic, sizeof(header.magic)) != 0)
{
throw std::runtime_error("Invalid snapshot");
}
if (default_header.version != header.version)
{
throw std::runtime_error("Unsupported snapshot version: " + std::to_string(header.version) +
"(needed: " + std::to_string(default_header.version) + ")");
}
return snapshot.subspan(sizeof(header));
}
std::vector<std::byte> get_compressed_emulator_state(const windows_emulator& win_emu)
{
utils::buffer_serializer serializer{};
win_emu.serialize(serializer);
return utils::compression::zlib::compress(serializer.get_buffer());
}
std::vector<std::byte> get_decompressed_emulator_state(const std::span<const std::byte> snapshot)
{
const auto data = validate_header(snapshot);
return utils::compression::zlib::decompress(data);
}
std::string get_main_executable_name(const windows_emulator& win_emu)
{
const auto* exe = win_emu.mod_manager.executable;
if (exe)
{
return std::filesystem::path(exe->name).stem().string();
}
return "process";
}
}
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu)
{
const auto state = get_compressed_emulator_state(win_emu);
snapshot_header header{};
std::span header_span(reinterpret_cast<const std::byte*>(&header), sizeof(header));
std::vector<std::byte> snapshot{};
snapshot.reserve(header_span.size() + state.size());
snapshot.assign(header_span.begin(), header_span.end());
snapshot.insert(snapshot.end(), state.begin(), state.end());
return snapshot;
}
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, const bool log)
{
std::filesystem::path snapshot_file =
get_main_executable_name(win_emu) + "-" + std::to_string(time(nullptr)) + ".snap";
if (log)
{
win_emu.log.log("Writing snapshot to %s...\n", snapshot_file.string().c_str());
}
const auto snapshot = create_emulator_snapshot(win_emu);
if (!utils::io::write_file(snapshot_file, snapshot))
{
throw std::runtime_error("Failed to write snapshot!");
}
return snapshot_file;
}
void load_emulator_snapshot(windows_emulator& win_emu, const std::span<const std::byte> snapshot)
{
const auto data = get_decompressed_emulator_state(snapshot);
utils::buffer_deserializer deserializer{data};
win_emu.deserialize(deserializer);
}
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file)
{
std::vector<std::byte> data{};
if (!utils::io::read_file(snapshot_file, &data))
{
throw std::runtime_error("Failed to read snapshot file: " + snapshot_file.string());
}
load_emulator_snapshot(win_emu, data);
}
}

View File

@@ -1,12 +1,12 @@
#pragma once
#include <windows_emulator.hpp>
namespace snapshot
{
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu);
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, bool log = true);
void load_emulator_snapshot(windows_emulator& win_emu, std::span<const std::byte> snapshot);
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file);
}
#pragma once
#include <windows_emulator.hpp>
namespace snapshot
{
std::vector<std::byte> create_emulator_snapshot(const windows_emulator& win_emu);
std::filesystem::path write_emulator_snapshot(const windows_emulator& win_emu, bool log = true);
void load_emulator_snapshot(windows_emulator& win_emu, std::span<const std::byte> snapshot);
void load_emulator_snapshot(windows_emulator& win_emu, const std::filesystem::path& snapshot_file);
}

View File

@@ -1,31 +1,31 @@
#pragma once
#include <map>
#include <set>
#include <list>
#include <array>
#include <deque>
#include <queue>
#include <thread>
#include <ranges>
#include <atomic>
#include <vector>
#include <mutex>
#include <string>
#include <chrono>
#include <memory>
#include <fstream>
#include <functional>
#include <filesystem>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <unordered_set>
#include <condition_variable>
#include <cassert>
#include <platform/platform.hpp>
// NOLINTNEXTLINE(google-global-names-in-headers)
using namespace std::literals;
#pragma once
#include <map>
#include <set>
#include <list>
#include <array>
#include <deque>
#include <queue>
#include <thread>
#include <ranges>
#include <atomic>
#include <vector>
#include <mutex>
#include <string>
#include <chrono>
#include <memory>
#include <fstream>
#include <functional>
#include <filesystem>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <unordered_set>
#include <condition_variable>
#include <cassert>
#include <platform/platform.hpp>
// NOLINTNEXTLINE(google-global-names-in-headers)
using namespace std::literals;

View File

@@ -0,0 +1,256 @@
#include "tenet_tracer.hpp"
#include <iomanip>
#include <map>
TenetTracer::TenetTracer(windows_emulator& win_emu, const std::string& log_filename)
: m_win_emu(win_emu), m_log_file(log_filename)
{
if (!m_log_file.is_open())
{
throw std::runtime_error("TenetTracer: Failed to open log file -> " + log_filename);
}
// Set up memory hooks.
auto& emu = m_win_emu.emu();
m_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);
});
m_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);
});
}
TenetTracer::~TenetTracer()
{
auto& emu = m_win_emu.emu();
if (m_read_hook) emu.delete_hook(m_read_hook);
if (m_write_hook) emu.delete_hook(m_write_hook);
// Filter and write the buffer when the program ends.
filter_and_write_buffer();
if (m_log_file.is_open())
{
m_log_file.close();
}
}
// Helper function: Parses a log line and adds register changes to the map.
static void parse_and_accumulate_changes(const std::string& line, std::map<std::string, std::string>& 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; // Updates existing or adds a new one.
}
start = end + 1;
}
}
void TenetTracer::filter_and_write_buffer()
{
if (m_raw_log_buffer.empty())
{
return;
}
const auto* exe_module = m_win_emu.mod_manager.executable;
if (!exe_module)
{
// If there is no main module, write the raw data and exit.
for (const auto& line : m_raw_log_buffer)
{
m_log_file << line << '\n';
}
return;
}
// Always write the first line (initial registers).
if (!m_raw_log_buffer.empty())
{
m_log_file << m_raw_log_buffer.front() << '\n';
}
bool currently_outside = false;
std::map<std::string, std::string> accumulated_changes;
for (size_t i = 1; i < m_raw_log_buffer.size(); ++i)
{
const auto& line = m_raw_log_buffer[i];
size_t rip_pos = line.find("rip=0x");
if (rip_pos == std::string::npos) continue;
char* end_ptr;
uint64_t address = std::strtoull(line.c_str() + rip_pos + 6, &end_ptr, 16);
bool is_line_inside = exe_module->is_within(address);
if (is_line_inside)
{
// We are inside the main module.
if (currently_outside)
{
// JUST ENTERED FROM OUTSIDE (moment of return from API)
// 1. Create a synthetic log line from the accumulated changes.
if (!accumulated_changes.empty())
{
std::stringstream summary_line;
bool first = true;
// Separate rip from the map as it will be added at the end.
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;
}
// Add the last known rip at the end.
if (!last_rip.empty())
{
if (!first) summary_line << ",";
summary_line << "rip=" << last_rip;
}
m_log_file << summary_line.str() << '\n';
}
accumulated_changes.clear();
}
// 2. Write the current line within the main module.
m_log_file << line << '\n';
currently_outside = false;
}
else
{
// We are outside the main module.
// 1. Accumulate the changes.
parse_and_accumulate_changes(line, accumulated_changes);
currently_outside = true;
}
}
m_raw_log_buffer.clear();
}
std::string TenetTracer::format_hex(uint64_t value)
{
std::stringstream ss;
ss << "0x" << std::hex << value;
return ss.str();
}
std::string TenetTracer::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<int>(data[i]);
}
return ss.str();
}
void TenetTracer::log_memory_read(uint64_t address, const void* data, size_t size)
{
if (!m_mem_read_log.str().empty())
{
m_mem_read_log << ";";
}
m_mem_read_log << format_hex(address) << ":" << format_byte_array(static_cast<const uint8_t*>(data), size);
}
void TenetTracer::log_memory_write(uint64_t address, const void* data, size_t size)
{
if (!m_mem_write_log.str().empty())
{
m_mem_write_log << ";";
}
m_mem_write_log << format_hex(address) << ":" << format_byte_array(static_cast<const uint8_t*>(data), size);
}
void TenetTracer::process_instruction(uint64_t address)
{
auto& emu = m_win_emu.emu();
std::stringstream trace_line;
std::array<uint64_t, GPRs_TO_TRACE.size()> current_regs;
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
{
current_regs[i] = emu.reg<uint64_t>(GPRs_TO_TRACE[i].first);
}
bool first_entry = true;
auto append_separator = [&]() {
if (!first_entry) {
trace_line << ",";
}
first_entry = false;
};
if (m_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]);
}
m_is_first_instruction = false;
}
else
{
for (size_t i = 0; i < GPRs_TO_TRACE.size(); ++i)
{
if (m_previous_regs[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);
std::string mem_reads = m_mem_read_log.str();
if (!mem_reads.empty()) {
append_separator();
trace_line << "mr=" << mem_reads;
}
std::string mem_writes = m_mem_write_log.str();
if (!mem_writes.empty()) {
append_separator();
trace_line << "mw=" << mem_writes;
}
// Add the data to the buffer instead of writing directly to the file.
m_raw_log_buffer.push_back(trace_line.str());
m_previous_regs = current_regs;
m_mem_read_log.str("");
m_mem_read_log.clear();
m_mem_write_log.str("");
m_mem_write_log.clear();
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <windows_emulator.hpp>
#include <emulator/x86_register.hpp>
#include <fstream>
#include <string>
#include <vector>
#include <array>
#include <sstream>
#include <string_view>
// List of registers to trace for the x64 architecture.
constexpr std::array<std::pair<x86_register, std::string_view>, 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 TenetTracer
{
public:
TenetTracer(windows_emulator& win_emu, const std::string& log_filename);
~TenetTracer();
// A new public method to call TenetTracer for each instruction.
void process_instruction(uint64_t address);
// Disable copy and move operations.
TenetTracer(const TenetTracer&) = delete;
TenetTracer& operator=(const TenetTracer&) = 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);
std::string format_hex(uint64_t value);
std::string format_byte_array(const uint8_t* data, size_t size);
windows_emulator& m_win_emu;
std::ofstream m_log_file;
// In-memory buffering for performance.
std::vector<std::string> m_raw_log_buffer;
// Use an array instead of a map to store the register state of the previous instruction.
std::array<uint64_t, GPRs_TO_TRACE.size()> m_previous_regs{};
bool m_is_first_instruction = true;
// To temporarily store memory operations.
std::stringstream m_mem_read_log;
std::stringstream m_mem_write_log;
// To manage memory hooks.
emulator_hook* m_read_hook = nullptr;
emulator_hook* m_write_hook = nullptr;
};

View File

@@ -1,26 +1,26 @@
import os
import subprocess
emulator_root = os.getenv('EMULATOR_ROOT')
analysis_sample = os.getenv('ANALYSIS_SAMPLE')
virtual_sample = 'C:/analysis-sample.exe'
application = 'analyzer'
def make_app(app):
if os.name == 'nt':
return app + ".exe"
return app
command = [
os.path.join(os.getcwd(), make_app(application)),
'-c',
'-e', emulator_root,
'-p', virtual_sample, analysis_sample,
virtual_sample
]
result = subprocess.run(command, cwd=os.getcwd())
exit(result.returncode)
import os
import subprocess
emulator_root = os.getenv('EMULATOR_ROOT')
analysis_sample = os.getenv('ANALYSIS_SAMPLE')
virtual_sample = 'C:/analysis-sample.exe'
application = 'analyzer'
def make_app(app):
if os.name == 'nt':
return app + ".exe"
return app
command = [
os.path.join(os.getcwd(), make_app(application)),
'-c',
'-e', emulator_root,
'-p', virtual_sample, analysis_sample,
virtual_sample
]
result = subprocess.run(command, cwd=os.getcwd())
exit(result.returncode)