mirror of
https://github.com/mirkobrombin/raven.git
synced 2026-11-03 21:46:32 +00:00
197 lines
11 KiB
C++
197 lines
11 KiB
C++
#include "environment.h"
|
|
#include "providers.h"
|
|
#include "module_api.h"
|
|
#include "loader.h"
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <stdexcept>
|
|
#include <sys/mman.h>
|
|
|
|
static std::vector<uint8_t> read_file(const char *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 environment 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 environment fixture");
|
|
return bytes;
|
|
}
|
|
|
|
static void check(bool condition, const char *message)
|
|
{
|
|
if (!condition)
|
|
throw std::runtime_error(message);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
try {
|
|
if (argc != 4)
|
|
throw std::runtime_error("bootstrap, client and environment probe required");
|
|
raven::ModuleSet modules;
|
|
raven::PEModule *entry = nullptr;
|
|
for (int index = 2; index < argc; ++index) {
|
|
auto bytes = read_file(argv[index]);
|
|
const auto base = raven::PEImage(bytes).image_base;
|
|
auto &module = modules.add(std::filesystem::path(argv[index]).filename().string(), std::move(bytes), base);
|
|
if (!entry)
|
|
entry = &module;
|
|
}
|
|
check(modules.bind_imports() == 4, "environment fixture import count differs");
|
|
std::vector<raven::LoadedImage *> images;
|
|
for (const auto &[name, module] : modules.all())
|
|
images.push_back(&module->loaded);
|
|
raven::NativeProcess process(images, argv[1], std::chrono::seconds(2));
|
|
raven::NativeEnvironment environment(process, entry->loaded.base(), 73, 91);
|
|
const auto loader = raven::initialize_loader_data(process, environment.peb(), modules, *entry);
|
|
check(loader != 0, "loader graph was not published");
|
|
bool loader_rejected = false;
|
|
try { raven::initialize_loader_data(process, environment.peb(), modules, *entry); }
|
|
catch (const std::runtime_error &) { loader_rejected = true; }
|
|
check(loader_rejected, "duplicate loader graph replaced the original");
|
|
const auto wrong_peb = process.map_memory(4096, PROT_READ | PROT_WRITE);
|
|
loader_rejected = false;
|
|
try { raven::initialize_loader_data(process, wrong_peb, modules, *entry); }
|
|
catch (const std::runtime_error &) { loader_rejected = true; }
|
|
check(loader_rejected && process.read_memory(wrong_peb + 0x18, 8) == std::vector<uint8_t>(8),
|
|
"mismatched PEB received a loader graph");
|
|
process.unmap_memory(wrong_peb);
|
|
// KUSER_SHARED_DATA: Windows 10.0.19045, a workstation, read-only to the program.
|
|
const auto shared = process.read_memory(0x7ffe0260, 20);
|
|
check(shared[0] == 0x65 && shared[1] == 0x4a && shared[4] == 1 && shared[12] == 10 && shared[16] == 0 &&
|
|
process.has_protection(0x7ffe0000, 4096, PROT_READ), "shared user data does not name Windows 10 22H2");
|
|
check(environment.last_error() == 0, "initial LastError is not zero");
|
|
environment.set_last_error(55);
|
|
const auto heap_list=environment.prepare_heaps();
|
|
check(heap_list && !environment.process_heap() && process.read_memory(heap_list,4096)==std::vector<uint8_t>(4096),
|
|
"initial process heap list is not owned zeroed storage");
|
|
bool heap_rejected=false;
|
|
try { environment.prepare_heaps(); } catch (const std::runtime_error &) { heap_rejected=true; }
|
|
check(heap_rejected,"duplicate heap storage replaced the owner");
|
|
heap_rejected=false;
|
|
try { environment.publish_process_heap(heap_list); } catch (const std::runtime_error &) { heap_rejected=true; }
|
|
check(heap_rejected && !environment.process_heap(),"unregistered heap was published");
|
|
check(environment.last_error() == 55, "host LastError state was not retained");
|
|
check(!environment.dispatch("NotImplemented"), "unknown API returned apparent success");
|
|
bool rejected = false;
|
|
try { environment.dispatch("GetLastError"); } catch (const std::runtime_error &) { rejected = true; }
|
|
check(rejected, "API dispatch outside a pending call was accepted");
|
|
rejected = false;
|
|
try { raven::NativeEnvironment duplicate(process, entry->loaded.base(), 73, 91); }
|
|
catch (const std::runtime_error &) { rejected = true; }
|
|
check(rejected, "duplicate thread environment was accepted");
|
|
auto saved = process.context();
|
|
auto wrong = saved;
|
|
wrong.registers.gs_base = 0;
|
|
process.set_context(wrong);
|
|
rejected = false;
|
|
try { environment.set_last_error(99); } catch (const std::runtime_error &) { rejected = true; }
|
|
process.set_context(saved);
|
|
check(rejected && environment.last_error() == 55, "wrong-thread LastError write was accepted");
|
|
std::map<uint64_t, std::string> traps;
|
|
for (const char *name : {"GetLastError", "SetLastError", "GetCurrentProcessId", "GetCurrentThreadId"}) {
|
|
const auto address = modules.resolve({"kernel32.dll", name, 0, false, 0});
|
|
process.add_api_trap(address);
|
|
traps.emplace(address, name);
|
|
}
|
|
auto result = process.call(entry->loaded.base() + entry->image.entry, {}, std::chrono::seconds(2));
|
|
size_t calls = 0;
|
|
while (result.stop == raven::NativeStop::api) {
|
|
check(++calls <= 32, "environment fixture exceeded API limit");
|
|
const auto found = traps.find(result.value);
|
|
check(found != traps.end() && environment.dispatch(found->second), "unrecognized environment API stop");
|
|
result = process.resume(std::chrono::seconds(2));
|
|
}
|
|
check(result.stop == raven::NativeStop::returned && result.value == 0 && calls == 8,
|
|
"compiled TEB/PEB and LastError fixture failed");
|
|
check(environment.last_error() == 0, "compiled final LastError differs");
|
|
raven::NativeProcess other(images, argv[1], std::chrono::seconds(2));
|
|
raven::NativeEnvironment isolated(other, entry->loaded.base(), 101, 103);
|
|
isolated.set_last_error(123);
|
|
check(isolated.last_error() == 123 && environment.last_error() == 0, "independent guest LastError states interfered");
|
|
raven::NativeModuleAPI module_api(process, environment, modules, *entry);
|
|
const auto proxy = modules.resolve({"kernel32.dll", "GetLastError", 0, false, 0});
|
|
const auto text = process.map_memory(4096, PROT_READ | PROT_WRITE);
|
|
const auto invoke = [&](std::string_view name, std::span<const uint64_t> arguments) {
|
|
check(process.call(proxy, arguments, std::chrono::seconds(2)).stop == raven::NativeStop::api, "module API test did not stop");
|
|
check(module_api.dispatch(name), "module API dispatch was not recognized");
|
|
return process.resume(std::chrono::seconds(2)).value;
|
|
};
|
|
const uint64_t null_name[] = {0};
|
|
environment.set_last_error(77);
|
|
check(invoke("GetModuleHandleA", null_name) == entry->loaded.base() && environment.last_error() == 77,
|
|
"null module lookup or LastError preservation differs");
|
|
const uint8_t kernel_name[] = {'K','E','R','N','E','L','3','2',0};
|
|
process.write_memory(text, kernel_name);
|
|
const uint64_t name_pointer[] = {text};
|
|
const auto kernel = modules.all().at("kernel32.dll")->loaded.base();
|
|
check(invoke("GetModuleHandleA", name_pointer) == kernel, "case-insensitive default DLL extension lookup differs");
|
|
const uint8_t export_name[] = {'G','e','t','L','a','s','t','E','r','r','o','r',0};
|
|
process.write_memory(text, export_name);
|
|
const uint64_t procedure_name[] = {kernel, text};
|
|
check(invoke("GetProcAddress", procedure_name) == proxy, "dynamic named export lookup differs");
|
|
const uint64_t ordinal[] = {kernel, 1};
|
|
check(invoke("GetProcAddress", ordinal) == proxy, "dynamic ordinal export lookup differs");
|
|
const uint8_t missing[] = {'m','i','s','s','i','n','g',0};
|
|
process.write_memory(text, missing);
|
|
check(invoke("GetModuleHandleA", name_pointer) == 0 && environment.last_error() == 126, "missing module error differs");
|
|
check(invoke("GetProcAddress", procedure_name) == 0 && environment.last_error() == 127, "missing export error differs");
|
|
const uint8_t unicode_name[] = {0xe9, 0, 0, 0};
|
|
process.write_memory(text, unicode_name);
|
|
check(invoke("GetModuleHandleW", name_pointer) == 0 && environment.last_error() == 120, "unsupported Unicode lookup reported success");
|
|
const uint8_t path_name[] = {'C',':','\\','k','e','r','n','e','l','3','2','.','d','l','l',0};
|
|
process.write_memory(text, path_name);
|
|
check(invoke("GetModuleHandleA", name_pointer) == 0 && environment.last_error() == 120, "unsupported path lookup matched a basename");
|
|
const uint8_t trailing_dot[] = {'k','e','r','n','e','l','3','2','.',0};
|
|
process.write_memory(text, trailing_dot);
|
|
check(invoke("GetModuleHandleA", name_pointer) == 0 && environment.last_error() == 126, "trailing dot incorrectly appended DLL extension");
|
|
const uint64_t invalid_pointer[] = {UINT64_MAX};
|
|
check(process.call(proxy, invalid_pointer, std::chrono::seconds(2)).stop == raven::NativeStop::api, "invalid pointer test did not stop");
|
|
rejected = false;
|
|
try { module_api.dispatch("GetModuleHandleW"); } catch (const std::runtime_error &) { rejected = true; }
|
|
check(rejected, "module API accepted unreadable guest memory");
|
|
process.return_api(0);
|
|
process.resume(std::chrono::seconds(2));
|
|
const auto system=[&](std::initializer_list<uint64_t> args) {
|
|
check(process.call(proxy,std::span(args.begin(),args.size()),std::chrono::seconds(1)).stop==raven::NativeStop::api &&
|
|
environment.dispatch("NtQuerySystemInformation"),"system information did not dispatch");
|
|
const auto result=process.resume(std::chrono::seconds(1));
|
|
check(result.stop==raven::NativeStop::returned,"system information did not return");
|
|
return result.value;
|
|
};
|
|
environment.set_last_error(71);
|
|
check(system({0,text,64,text+128})==0 && environment.last_error()==71 && process.read_memory(text+128,4)[0]==64,
|
|
"basic system information length or LastError differs");
|
|
const auto basic=process.read_memory(text,64);
|
|
check(basic[8]==0 && basic[9]==16 && basic[24]==0 && basic[25]==0 && basic[26]==1 && basic[56] && basic[56]<=64,
|
|
"basic system geometry or processor group differs");
|
|
check(system({0,text,63,text+128})==0xc0000004U && system({0,text,65,0})==0xc0000004U &&
|
|
system({1,text,64,0})==0xc0000002U && process.read_memory(text,64)==basic,"unsupported system query changed output");
|
|
check(system({0,0x400000000000ULL,64,0})==0xc0000005U &&
|
|
system({0,text,64,0x400000000000ULL})==0xc0000005U && process.read_memory(text,64)==basic,
|
|
"bad system query output succeeded or changed data");
|
|
process.protect_memory(text,4096,PROT_READ);
|
|
check(system({0,text,64,0})==0xc0000005U && environment.last_error()==71,"readonly system query succeeded");
|
|
process.protect_memory(text,4096,PROT_READ | PROT_WRITE);
|
|
process.unmap_memory(text);
|
|
raven::ModuleSet runtime;
|
|
const std::span<const uint8_t> entry_bytes = entry->bytes;
|
|
auto &runtime_entry = runtime.add(entry->name, std::vector<uint8_t>(entry_bytes.begin(), entry_bytes.end()), entry->loaded.base());
|
|
for (size_t run = 0; run < 2; ++run) {
|
|
const auto executed = raven::run_native_process(runtime, runtime_entry, argv[1], std::chrono::seconds(2));
|
|
check(executed.stop == raven::NativeStop::returned && executed.value == 0,
|
|
"runtime-owned API providers did not survive module graph reuse");
|
|
}
|
|
std::cout << "Compiled PE64 GS/TEB/PEB, four environment APIs, guest LastError writes and independent guest state passed\n";
|
|
return 0;
|
|
} catch (const std::exception &error) {
|
|
std::cerr << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
}
|