#include "module_set.h" #include #include #include #include static void check(bool condition, const char *message) { if (!condition) throw std::runtime_error(message); } static void put(std::span bytes, size_t offset, uint64_t value, size_t width = 4) { for (size_t i = 0; i < width; ++i) bytes[offset + i] = static_cast(value >> (i * 8)); } static uint64_t read(std::span bytes, size_t offset, size_t width) { uint64_t value = 0; for (size_t i = 0; i < width; ++i) value |= uint64_t(bytes[offset + i]) << (i * 8); return value; } static void text(std::span bytes, size_t offset, const std::string &value) { std::copy(value.begin(), value.end(), bytes.begin() + static_cast(offset)); bytes[offset + value.size()] = 0; } static std::vector fixture(bool is_64, uint64_t base) { std::vector bytes(0x800); put(bytes, 0, 0x5a4d, 2); put(bytes, 0x3c, 0x80); put(bytes, 0x80, 0x4550); put(bytes, 0x84, is_64 ? 0x8664 : 0x14c, 2); put(bytes, 0x86, 1, 2); put(bytes, 0x94, is_64 ? 240 : 224, 2); put(bytes, 0x96, 0x2002, 2); put(bytes, 0x98, is_64 ? 0x20b : 0x10b, 2); put(bytes, 0xa8, 0x1000); put(bytes, is_64 ? 0xb0 : 0xb4, base, is_64 ? 8 : 4); put(bytes, 0xb8, 0x1000); put(bytes, 0xbc, 0x200); put(bytes, 0xd0, 0x2000); put(bytes, 0xd4, 0x200); put(bytes, is_64 ? 0x104 : 0xf4, 16); const size_t section = is_64 ? 0x188 : 0x178; text(bytes, section, ".text"); put(bytes, section + 8, 0x600); put(bytes, section + 12, 0x1000); put(bytes, section + 16, 0x600); put(bytes, section + 20, 0x200); put(bytes, section + 36, 0xe0000020); bytes[0x200] = 0xc3; return bytes; } static void exports(std::vector &bytes, bool is_64, const std::string &forwarder) { const size_t directory = is_64 ? 0x108 : 0xf8; put(bytes, directory, 0x1100); put(bytes, directory + 4, 0x100); put(bytes, 0x310, 7); put(bytes, 0x314, 3); put(bytes, 0x318, 3); put(bytes, 0x31c, 0x1140); put(bytes, 0x320, 0x1150); put(bytes, 0x324, 0x1160); put(bytes, 0x340, 0x1000); put(bytes, 0x348, forwarder.empty() ? 0x1010 : 0x11c0); put(bytes, 0x350, 0x1180); put(bytes, 0x354, 0x1190); put(bytes, 0x358, 0x11a0); put(bytes, 0x360, 0, 2); put(bytes, 0x362, 2, 2); put(bytes, 0x364, 0, 2); text(bytes, 0x380, "Alias"); text(bytes, 0x390, "Forward"); text(bytes, 0x3a0, "Function"); if (!forwarder.empty()) text(bytes, 0x3c0, forwarder); } static void imports(std::vector &bytes, bool is_64) { const size_t directory = is_64 ? 0x110 : 0x100; const size_t width = is_64 ? 8 : 4; put(bytes, directory, 0x1300); put(bytes, directory + 4, 40); put(bytes, 0x500, 0x1340); put(bytes, 0x50c, 0x13c0); put(bytes, 0x510, 0x1380); put(bytes, 0x540, 0x13e0, width); put(bytes, 0x540 + width, (uint64_t(1) << (width * 8 - 1)) | 9, width); text(bytes, 0x5c0, "Facade.DLL"); text(bytes, 0x5e2, "Function"); } static void rejected(const std::function &operation) { try { operation(); } catch (const std::runtime_error &) { return; } throw std::runtime_error("invalid export or module graph was accepted"); } int main() { try { for (bool is_64 : {false, true}) { const uint64_t root_base = is_64 ? 0x140000000ULL : 0x400000; const uint64_t facade_base = root_base + 0x100000; const uint64_t target_base = root_base + 0x200000; const size_t width = is_64 ? 8 : 4; auto root = fixture(is_64, root_base); auto facade = fixture(is_64, facade_base); auto target = fixture(is_64, target_base); imports(root, is_64); exports(facade, is_64, "TARGET.#7"); exports(target, is_64, ""); const auto decoded = raven::PEImage(facade).exports(); check(decoded.size() == 3 && decoded[0].ordinal == 7 && decoded[0].address == 0x1000 && decoded[0].names == std::vector{"Alias", "Function"} && decoded[1].address == 0 && decoded[2].forwarder == "TARGET.#7", "export table identities differ"); raven::ModuleSet modules; auto &main = modules.add("program.exe", root, root_base); modules.add("FaCaDe", facade, facade_base); const auto original = std::vector(main.loaded.data().begin(), main.loaded.data().end()); rejected([&] { modules.bind_imports(); }); check(std::equal(original.begin(), original.end(), main.loaded.data().begin()), "failed binding changed an image"); modules.add("TARGET.dll", target, target_base); check(modules.bind_imports() == 2 && read(main.loaded.data(), 0x1380, width) == facade_base + 0x1000 && read(main.loaded.data(), 0x1380 + width, width) == target_base + 0x1000, "IAT name/ordinal or forwarded address differs"); check(modules.bind_imports() == 2, "rebinding did not use immutable original import identities"); check(modules.resolve({"facade", "Alias", 0, false, 0}) == facade_base + 0x1000, "export alias did not resolve"); { auto relocated = target; const size_t relocation = is_64 ? 0x130 : 0x120; put(relocated, relocation, 0x1500); put(relocated, relocation + 4, 12); put(relocated, 0x700, 0x1000); put(relocated, 0x704, 12); put(relocated, 0x708, ((is_64 ? 10 : 3) << 12) | 0x520, 2); put(relocated, 0x720, target_base + 0x1000, width); raven::ModuleSet rebased; auto &client = rebased.add("main.exe", root, root_base); rebased.add("facade", facade, facade_base); auto &dependency = rebased.add("target", relocated, target_base + 0x10000); check(rebased.bind_imports() == 2 && read(client.loaded.data(), 0x1380 + width, width) == target_base + 0x11000 && read(dependency.loaded.data(), 0x1520, width) == target_base + 0x11000, "forwarded IAT or relocation retained the dependency's preferred base"); raven::ModuleSet automatic; auto &automatic_client = automatic.add("main.exe", root); automatic.add("facade", facade); automatic.add("occupied", target); const uint64_t fallback = is_64 ? 0x180000000ULL : 0x10000000ULL; automatic.add("reserved", fixture(is_64, fallback)); auto &moved = automatic.add("target", relocated); check(automatic_client.loaded.base() == root_base && moved.loaded.base() == fallback + 65536 && read(moved.loaded.data(), 0x1520, width) == moved.loaded.base() + 0x1000, "automatic placement changed a free preference or failed to skip occupied ranges"); check(automatic.bind_imports() == 2 && read(automatic_client.loaded.data(), 0x1380 + width, width) == moved.loaded.base() + 0x1000, "automatic placement left a forwarded IAT at the preferred base"); const auto count = automatic.all().size(); rejected([&] { automatic.add("fixed", target); }); check(automatic.all().size() == count, "failed automatic relocation published a module"); rejected([&] { automatic.add("requested", relocated, target_base); }); check(automatic.all().size() == count, "explicit colliding base changed the module graph"); raven::ModuleSet constrained; const auto image_size = raven::PEImage(relocated).image_size; const auto available = [&](uint64_t base, uint64_t size) { check(size == image_size, "placement queried the wrong image size"); return base != target_base && (base < fallback || base >= fallback + 2 * 65536); }; auto &placed = constrained.add("target", relocated, available); check(placed.loaded.base() == fallback + 2 * 65536 && read(placed.loaded.data(), 0x1520, width) == placed.loaded.base() + 0x1000, "placement ignored occupied process memory or failed to relocate"); auto &data = constrained.add_data("resource", relocated, available); check(data.loaded.base() == fallback + 3 * 65536 && read(data.loaded.data(), 0x1520, width) == target_base + 0x1000, "data placement overlapped a process range or applied code relocations"); raven::ModuleSet displaced; auto &displaced_first = displaced.add("first", relocated); check(displaced_first.loaded.base() == target_base, "initial relocatable placement ignored free preferred base"); auto &displaced_fixed = displaced.add("fixed", target); check(displaced_fixed.loaded.base() == target_base && displaced_first.loaded.base() == fallback && read(displaced_first.loaded.data(), 0x1520, width) == fallback + 0x1000, "fixed-base module did not displace an unbound relocatable module"); } rejected([&] { modules.resolve({"facade", "function", 0, false, 0}); }); rejected([&] { modules.resolve({"facade", "", 8, true, 0}); }); rejected([&] { modules.add("facade.dll", facade, facade_base); }); rejected([&] { modules.add("overlap.dll", root, root_base); }); rejected([&] { modules.add("../target.dll", target, target_base); }); for (const auto &forwarder : {"target.Function", "target.dll.Function", "target.#7"}) { auto bytes = facade; exports(bytes, is_64, forwarder); raven::ModuleSet named; named.add("facade", bytes, facade_base); named.add("target", target, target_base); check(named.resolve({"facade", "Forward", 0, false, 0}) == target_base + 0x1000, "forwarder name or extension resolution differs"); } for (const auto &forwarder : {"facade.#9", "target.Forward", "target.#no", "target.#", "target.#4294967296", "target.", "NoSeparator"}) { auto bytes = facade; exports(bytes, is_64, forwarder); auto other = target; exports(other, is_64, "facade.Forward"); raven::ModuleSet cyclic; cyclic.add("facade", bytes, facade_base); cyclic.add("target", other, target_base); rejected([&] { cyclic.resolve({"facade", "Forward", 0, false, 0}); }); } const size_t directory = is_64 ? 0x108 : 0xf8; for (const auto &[offset, value] : std::vector>{ {directory + 4, 39}, {0x310, UINT32_MAX}, {0x314, 1000001}, {0x31c, 0}, {0x320, 0x1fff}, {0x324, 0x1fff}, {0x360, 3}, {0x354, 0x1180}, {0x340, 0x1f00}, {0x348, 0x11ff}}) { auto bytes = facade; put(bytes, offset, value, offset == 0x360 ? 2 : 4); if (offset == 0x348) bytes[0x3ff] = 'x'; rejected([&] { raven::PEImage(bytes).exports(); }); } auto hole = root; put(hole, 0x510, 0x1800); raven::ModuleSet bad_iat; bad_iat.add("main.exe", hole, root_base); bad_iat.add("facade", facade, facade_base); bad_iat.add("target", target, target_base); rejected([&] { bad_iat.bind_imports(); }); raven::ModuleSet mixed; mixed.add("target", target, target_base); rejected([&] { mixed.add("other", fixture(!is_64, 0x700000), 0x700000); }); } { // A PE32 library of managed code alone, widened as a 64-bit process loads it, keeps its 32-bit fixup and applies the low half // of the delta to it. const uint64_t base = 0x10000000; auto managed = fixture(false, base); put(managed, 0x168, 0x1400); put(managed, 0x16c, 0x48); put(managed, 0x600, 0x48); put(managed, 0x610, 1); put(managed, 0x120, 0x1500); put(managed, 0x124, 12); put(managed, 0x700, 0x1000); put(managed, 0x704, 12); put(managed, 0x708, (3 << 12) | 0x520, 2); put(managed, 0x720, base + 0x1000); check(raven::widen_il_only_image(managed), "managed PE32 library was not widened"); raven::ModuleSet widened; auto &library = widened.add("managed.dll", managed, base + 0x10000); check(library.image.is_64 && library.image.il_only() && read(library.loaded.data(), 0x1520, 4) == base + 0x11000 && read(library.loaded.data(), 0x1524, 4) == 0, "widened library lost or widened its 32-bit fixup"); auto wrong = fixture(true, 0x180000000ULL); put(wrong, 0x130, 0x1500); put(wrong, 0x134, 12); put(wrong, 0x700, 0x1000); put(wrong, 0x704, 12); put(wrong, 0x708, (7 << 12) | 0x520, 2); rejected([&] { raven::ModuleSet other; other.add("wrong", wrong, 0x180010000ULL); }); } std::cout << "PE32/PE64 exports, aliases, holes, forwarding, transactional IAT binding, widened managed libraries and negative module contracts passed\n"; return 0; } catch (const std::exception &error) { std::cerr << error.what() << '\n'; return 1; } }