mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-10-26 21:17:40 +00:00
219 lines
11 KiB
C++
219 lines
11 KiB
C++
#include "loader.h"
|
|
#include "providers.h"
|
|
#include "environment.h"
|
|
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <signal.h>
|
|
#include <stdexcept>
|
|
#include <sys/mman.h>
|
|
|
|
static std::vector<uint8_t> read_file(const std::filesystem::path &path)
|
|
{
|
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
|
if (!file || file.tellg() <= 0 || file.tellg() > 1024 * 1024)
|
|
throw std::runtime_error("cannot read DLL lifecycle fixture");
|
|
std::vector<uint8_t> bytes(static_cast<size_t>(file.tellg()));
|
|
file.seekg(0);
|
|
if (!file.read(reinterpret_cast<char *>(bytes.data()), static_cast<std::streamsize>(bytes.size())))
|
|
throw std::runtime_error("truncated DLL lifecycle fixture");
|
|
return bytes;
|
|
}
|
|
|
|
static void check(bool condition, const char *message)
|
|
{
|
|
if (!condition)
|
|
throw std::runtime_error(message);
|
|
}
|
|
|
|
static uint32_t field(const raven::ModuleSet &modules, const std::string &name, const std::string &symbol)
|
|
{
|
|
auto &module = *modules.all().at(name);
|
|
const auto address = modules.resolve({name, symbol, 0, false, 0});
|
|
const auto data = module.loaded.data().subspan(address - module.loaded.base(), 4);
|
|
uint32_t result = 0;
|
|
for (size_t byte = 0; byte < data.size(); ++byte)
|
|
result |= uint32_t(data[byte]) << (byte * 8);
|
|
return result;
|
|
}
|
|
|
|
static uint64_t pointer(raven::NativeProcess &process, uint64_t address)
|
|
{
|
|
const auto bytes = process.read_memory(address, 8);
|
|
uint64_t value = 0;
|
|
for (size_t index = 0; index < bytes.size(); ++index)
|
|
value |= uint64_t(bytes[index]) << (index * 8);
|
|
return value;
|
|
}
|
|
|
|
static void dynamic_lists(const std::filesystem::path &directory, const std::string &bootstrap)
|
|
{
|
|
raven::ModuleSet modules;
|
|
auto &entry = modules.add("client.exe", read_file(directory / "client.exe"));
|
|
entry.imports.clear();
|
|
entry.loaded.data()[entry.image.entry] = 0xcc;
|
|
entry.loaded.data()[entry.image.entry + 1] = 0xc3;
|
|
raven::LoadedImage *images[] = {&entry.loaded};
|
|
raven::NativeProcess process(images, bootstrap, std::chrono::seconds(10));
|
|
raven::NativeEnvironment environment(process, entry.loaded.base(), 1, 1);
|
|
raven::NativeLoaderData loader(process, environment.peb(), modules, entry);
|
|
const auto initial_node = pointer(process, loader.address() + 0x10);
|
|
const auto initial_fields = process.read_memory(initial_node + 0x30, 0xf0);
|
|
const auto rejected = [&](const auto &operation) {
|
|
bool failed = false;
|
|
try { operation(); } catch (const std::runtime_error &) { failed = true; }
|
|
check(failed, "invalid dynamic loader list operation succeeded");
|
|
};
|
|
const auto list = [&](size_t head_offset, size_t member) {
|
|
const uint64_t head = loader.address() + head_offset;
|
|
std::vector<uint64_t> result;
|
|
uint64_t previous = head;
|
|
uint64_t current = pointer(process, head);
|
|
while (current != head) {
|
|
check(result.size() < 10 && pointer(process, current + 8) == previous,
|
|
"dynamic loader list backward link differs");
|
|
result.push_back(pointer(process, current - member + 0x30));
|
|
previous = current;
|
|
current = pointer(process, current);
|
|
}
|
|
check(pointer(process, head + 8) == previous, "dynamic loader tail link differs");
|
|
return result;
|
|
};
|
|
process.add_api_trap(entry.loaded.base() + entry.image.entry);
|
|
const uint64_t arguments[] = {17, 23};
|
|
check(process.call(entry.loaded.base() + entry.image.entry, arguments, std::chrono::seconds(10)).stop == raven::NativeStop::api,
|
|
"dynamic loader list fixture did not reach API suspension");
|
|
const auto before = process.context();
|
|
auto &base = modules.add("base.dll", read_file(directory / "base.dll"));
|
|
auto &dependent = modules.add("dependent.dll", read_file(directory / "dependent.dll"));
|
|
rejected([&] { loader.add(base); });
|
|
process.map_image(base.loaded);
|
|
process.map_image(dependent.loaded);
|
|
loader.add(base);
|
|
loader.add(dependent);
|
|
rejected([&] { loader.add(base); });
|
|
rejected([&] { loader.remove(entry); });
|
|
const std::vector<uint64_t> expected{entry.loaded.base(), base.loaded.base(), dependent.loaded.base()};
|
|
check(list(0x10, 0) == expected && list(0x20, 0x10) == expected && list(0x30, 0x20).empty(),
|
|
"new DLL was missing or initialized before notification");
|
|
loader.initialized(base);
|
|
rejected([&] { loader.initialized(base); });
|
|
const auto old_loader = process.read_memory(environment.peb() + 0x18, 8);
|
|
process.write_memory(environment.peb() + 0x18, std::vector<uint8_t>(8));
|
|
rejected([&] { loader.initialized(dependent); });
|
|
process.write_memory(environment.peb() + 0x18, old_loader);
|
|
const auto old_head = process.read_memory(loader.address() + 0x10, 16);
|
|
process.write_memory(loader.address() + 0x10, std::vector<uint8_t>(16));
|
|
rejected([&] { loader.initialized(dependent); });
|
|
process.write_memory(loader.address() + 0x10, old_head);
|
|
const auto dependent_node = pointer(process, loader.address() + 0x18);
|
|
const auto dependent_fields = process.read_memory(dependent_node, 0x120);
|
|
process.protect_memory(loader.address(), 4096, PROT_READ);
|
|
rejected([&] { loader.initialized(dependent); });
|
|
process.protect_memory(loader.address(), 4096, PROT_READ | PROT_WRITE);
|
|
check(process.read_memory(dependent_node, 0x120) == dependent_fields &&
|
|
list(0x30, 0x20) == std::vector<uint64_t>{base.loaded.base()}, "failed list publication was not rolled back");
|
|
loader.initialized(dependent);
|
|
check(list(0x30, 0x20) == std::vector<uint64_t>{base.loaded.base(), dependent.loaded.base()},
|
|
"dynamic initialization list order differs");
|
|
loader.remove(base);
|
|
process.unmap_image(base.loaded.base());
|
|
check(list(0x10, 0) == std::vector<uint64_t>{entry.loaded.base(), dependent.loaded.base()} &&
|
|
list(0x30, 0x20) == std::vector<uint64_t>{dependent.loaded.base()}, "middle DLL removal broke links");
|
|
loader.remove(dependent);
|
|
process.unmap_image(dependent.loaded.base());
|
|
rejected([&] { process.read_memory(dependent_node, 1); });
|
|
rejected([&] { loader.remove(dependent); });
|
|
check(list(0x10, 0) == std::vector<uint64_t>{entry.loaded.base()} && list(0x30, 0x20).empty() &&
|
|
pointer(process, environment.peb() + 0x18) == loader.address() &&
|
|
pointer(process, loader.address() + 0x10) == initial_node &&
|
|
process.read_memory(initial_node + 0x30, 0xf0) == initial_fields,
|
|
"dynamic lists replaced initial loader pointers or metadata");
|
|
const auto after = process.context();
|
|
check(after.registers.rip == before.registers.rip && after.registers.rsp == before.registers.rsp &&
|
|
process.api_arguments(2) == std::vector<uint64_t>{17, 23}, "dynamic list operations lost pending API context");
|
|
process.return_api(88);
|
|
check(process.resume(std::chrono::seconds(10)).value == 88, "dynamic list operations lost guest return");
|
|
std::cout << "Dynamic PEB list publication, stable nodes, initialization transitions, removal and rollback passed\n";
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
try {
|
|
if (argc != 3)
|
|
throw std::runtime_error("bootstrap and lifecycle fixture directory required");
|
|
const std::filesystem::path directory(argv[2]);
|
|
dynamic_lists(directory, argv[1]);
|
|
for (const std::string extra : {"", "exit", "reject.dll", "fault.dll", "loop.dll"}) {
|
|
const bool explicit_exit = extra == "exit";
|
|
raven::ModuleSet modules;
|
|
raven::PEModule *entry = nullptr;
|
|
for (const std::string name : std::vector<std::string>{"client.exe", "dependent.dll", "base.dll", explicit_exit ? "" : extra}) {
|
|
if (name.empty())
|
|
continue;
|
|
auto bytes = read_file(directory / name);
|
|
const auto base = raven::PEImage(bytes).image_base;
|
|
auto &module = modules.add(name, std::move(bytes), base);
|
|
if (!entry)
|
|
entry = &module;
|
|
}
|
|
if (explicit_exit) {
|
|
const auto address = modules.resolve({"client.exe", "exit_requested", 0, false, 0});
|
|
entry->loaded.data()[address - entry->loaded.base()] = 1;
|
|
}
|
|
// The looping DLL must still be reached after base.dll and dependent.dll attach, which can take most of a second on a loaded host.
|
|
const auto timeout = std::chrono::milliseconds(extra == "loop.dll" ? 2000 : 10000);
|
|
if (extra == "reject.dll") {
|
|
bool rejected = false;
|
|
try { raven::run_native_process(modules, *entry, argv[1], timeout); }
|
|
catch (const std::runtime_error &error) { rejected = std::string(error.what()) == "DLL_PROCESS_ATTACH failed: reject.dll"; }
|
|
check(rejected, "failed DLL attach did not abort startup");
|
|
check(field(modules, "base.dll", "shutdown_state") == 0, "DLL attach rollback observed process shutdown");
|
|
check(field(modules, "base.dll", "base_state") == 1125634 &&
|
|
field(modules, "dependent.dll", "dependent_state") == 210 &&
|
|
field(modules, "reject.dll", "reject_state") == 510, "DLL failure rollback order or reserved argument differs");
|
|
} else {
|
|
const auto result = raven::run_native_process(modules, *entry, argv[1], timeout);
|
|
if (extra.empty() || explicit_exit) {
|
|
check(result.stop == (explicit_exit ? raven::NativeStop::exited : raven::NativeStop::returned) && result.value == 0,
|
|
"DLL attach order failed the executable workflow");
|
|
check(field(modules, "base.dll", "shutdown_state") == 1, "DLL process detach did not observe shutdown");
|
|
check(field(modules, "base.dll", "base_state") == 11234 &&
|
|
field(modules, "dependent.dll", "dependent_state") == 210, "DLL process detach order differs");
|
|
auto &base = *modules.all().at("base.dll");
|
|
auto &dependent = *modules.all().at("dependent.dll");
|
|
const auto precedes = [](const auto &order, const auto *first, const auto *second) {
|
|
return std::find(order.begin(), order.end(), first) < std::find(order.begin(), order.end(), second);
|
|
};
|
|
auto order = raven::initialization_order(modules, *entry);
|
|
check(precedes(order, &base, &dependent) && precedes(order, &dependent, entry), "static dependency initialization order differs");
|
|
// Exercise activated forwarder edges independently of guest execution.
|
|
base.exports.push_back({500, 1, {"Through"}, "dependent.DependentState"});
|
|
dependent.imports.clear();
|
|
entry->imports = {{"base.dll", "Through", 0, false, 0}};
|
|
check(modules.resolution_path(entry->imports.front()) == std::vector<const raven::PEModule *>{&base, &dependent}, "forwarder resolution path differs");
|
|
order = raven::initialization_order(modules, *entry);
|
|
check(precedes(order, &dependent, &base) && precedes(order, &base, entry), "forwarded dependency initializes after its facade");
|
|
dependent.imports = {{"base.dll", "Query", 0, false, 0}};
|
|
order = raven::initialization_order(modules, *entry);
|
|
check(order.size() == modules.all().size() && std::count(order.begin(), order.end(), &base) == 1 &&
|
|
std::count(order.begin(), order.end(), &dependent) == 1, "cyclic dependencies initialize more than once");
|
|
} else {
|
|
check(result.stop == (extra == "loop.dll" ? raven::NativeStop::timeout : raven::NativeStop::fault), "DLL fault or timeout produced apparent startup success");
|
|
if (extra == "fault.dll")
|
|
check(result.signal == SIGILL, "DLL instruction fault signal differs");
|
|
check(field(modules, "base.dll", "base_state") == 1125, "DLL fault/timeout ran extra guest cleanup code");
|
|
}
|
|
}
|
|
check(field(modules, "client.exe", "entered") == (extra.empty() || explicit_exit ? 1U : 0U), "executable entry ran after DLL initialization failed");
|
|
}
|
|
std::cout << "Compiled DLL dependency attach, reverse detach, rejected attach rollback, fault/timeout and blocked EXE entry passed\n";
|
|
return 0;
|
|
} catch (const std::exception &error) {
|
|
std::cerr << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
}
|