#include "resources.h" #include #include #include namespace raven { NativeResources::NativeResources(NativeProcess &process, NativeEnvironment &environment, const ModuleSet &modules, const PEModule &entry) : process(process), environment(environment), modules(modules), entry(entry) {} const PEModule *NativeResources::module(uint64_t handle) { if (!handle) return &entry; for (const auto &[name, image] : modules.all()) if (image->loaded.base() == handle) return image.get(); environment.set_last_error(6); return nullptr; } const std::vector &NativeResources::resources(const PEModule &image) const { auto &cached = parsed[&image]; const auto directory = image.image.directories[2]; if (!directory.address && !directory.size) { cached = {}; return cached.resources; } // Self-extracting packages carry their whole payload as resources: the directory may be as large as the image that holds it. if (uint64_t(directory.address) + directory.size > image.image.image_size) throw std::runtime_error("native resource directory lies outside its image"); // The image the runtime maps for the guest is the live one, guest changes included. const auto live = image.loaded.data(); if (cached.valid && uint64_t(directory.address) + directory.size <= live.size()) { size_t at = 0; bool same = true; for (const auto &[offset, size] : cached.read) { if (std::memcmp(live.data() + directory.address + offset, cached.bytes.data() + at, size)) { same = false; break; } at += size; } if (same) return cached.resources; } // Read the live image, including guest changes, through the existing permission boundary. const auto bytes = process.read_memory(image.loaded.base() + directory.address, directory.size); cached = {}; std::vector> read; cached.resources = read_resources(directory, bytes, image.image.image_size, &read); // The parse reads a few bytes at a time; touching and overlapping parts are joined so that a lookup compares a few long runs. std::sort(read.begin(), read.end()); for (const auto &[offset, size] : read) if (!cached.read.empty() && offset <= cached.read.back().first + cached.read.back().second) cached.read.back().second = std::max(cached.read.back().second, offset + size - cached.read.back().first); else cached.read.emplace_back(offset, size); for (const auto &[offset, size] : cached.read) cached.bytes.insert(cached.bytes.end(), bytes.begin() + ptrdiff_t(offset), bytes.begin() + ptrdiff_t(offset + size)); cached.valid = true; return cached.resources; } std::optional NativeResources::key(uint64_t address, bool wide) { if (address <= UINT16_MAX) return static_cast(address); std::u16string text; const size_t width = wide ? 2 : 1; for (size_t index = 0; index < 32768; ++index) { if (address > UINT64_MAX - index * width) throw std::runtime_error("native resource string address overflows"); const auto bytes = process.read_memory(address + index * width, width); const uint32_t character = uint32_t(bytes[0]) | (wide ? uint32_t(bytes[1]) << 8 : 0); if (!character) { if (text.empty() || text[0] != u'#') return text; uint32_t value = 0; for (size_t digit = 1; digit < text.size(); ++digit) { if (text[digit] < u'0' || text[digit] > u'9' || value > (0x7fffffffU - uint32_t(text[digit] - u'0')) / 10) { environment.set_last_error(87); return std::nullopt; } value = value * 10 + uint32_t(text[digit] - u'0'); } if (text.size() == 1) { environment.set_last_error(87); return std::nullopt; } return value; } if (!wide && character > 127) { environment.set_last_error(120); return std::nullopt; } text += static_cast(character); } throw std::runtime_error("native resource string exceeds initial limit"); } static bool matches(const ResourceKey &left, const ResourceKey &right) { if (left.index() != right.index()) return false; if (std::holds_alternative(left)) return left == right; const auto fold = [](char16_t c) { return c >= u'a' && c <= u'z' ? static_cast(c - (u'a' - u'A')) : c; }; const auto &a = std::get(left); const auto &b = std::get(right); return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [&](auto x, auto y) { return fold(x) == fold(y); }); } uint64_t NativeResources::find(uint64_t handle, uint64_t type, uint64_t name, uint16_t language, bool wide) { const auto *image = module(handle); if (!image) return 0; const auto type_key = key(type, wide); const auto name_key = key(name, wide); if (!type_key || !name_key) return 0; const auto &data = resources(*image); std::vector candidates; bool has_type = false; for (const auto &resource : data) if (matches(resource.type, *type_key)) { has_type = true; if (matches(resource.name, *name_key)) candidates.push_back(&resource); } if (candidates.empty()) { const auto non_ascii = [](const ResourceKey &value) { const auto *text = std::get_if(&value); return text && std::any_of(text->begin(), text->end(), [](auto c) { return c > 127; }); }; environment.set_last_error(non_ascii(*type_key) || non_ascii(*name_key) ? 120 : data.empty() ? 1812 : has_type ? 1814 : 1813); return 0; } const PEResource *selected = nullptr; for (const auto desired : {uint32_t(language), uint32_t(language & 0x3ffU), uint32_t(0)}) { const auto found = std::find_if(candidates.begin(), candidates.end(), [&](const auto *r) { return r->language == desired; }); if (found != candidates.end()) { selected = *found; break; } } if (!selected && !language && candidates.size() == 1) selected = candidates[0]; // A neutral request over several languages takes English, as the guest's default locale is en-US. if (!selected && !language) { const auto english = std::find_if(candidates.begin(), candidates.end(), [](const auto *r) { return r->language == 0x409; }); const auto primary = std::find_if(candidates.begin(), candidates.end(), [](const auto *r) { return (r->language & 0x3ffU) == 0x09; }); if (english != candidates.end()) selected = *english; else if (primary != candidates.end()) selected = *primary; } if (!selected) { // Locale-dependent fallback needs thread NLS state; never invent a preferred language. environment.set_last_error(language ? 1815 : 120); return 0; } return image->loaded.base() + selected->entry_address; } uint64_t NativeResources::access(uint64_t handle, uint64_t resource, bool size) { const auto *image = module(handle); if (!image) return 0; for (const auto &item : resources(*image)) if (resource == image->loaded.base() + item.entry_address) { if (size) return item.size; if (item.size > 256 * 1024 * 1024) throw std::runtime_error("native resource payload exceeds initial policy limit"); process.read_memory(image->loaded.base() + item.data_address, item.size); return image->loaded.base() + item.data_address; } environment.set_last_error(6); return 0; } std::optional NativeResources::view(uint64_t handle, uint32_t type, uint64_t name, uint16_t language) { const auto found = find(handle, type, name, language, true); if (!found) return std::nullopt; const auto *image = module(handle); if (!image) return std::nullopt; for (const auto &item : resources(*image)) { if (found != image->loaded.base() + item.entry_address) continue; if (item.size > 128 * 1024 * 1024) throw std::runtime_error("native resource view exceeds payload policy"); return NativeResourceView{image->loaded.base() + item.data_address, item.size, item.language}; } environment.set_last_error(6); return std::nullopt; } uint64_t NativeResources::lock(uint64_t data) { for (const auto &[name, image] : modules.all()) if (data >= image->loaded.base() && data - image->loaded.base() < image->image.image_size) for (const auto &resource : resources(*image)) if (data == image->loaded.base() + resource.data_address) { if (resource.size > 256 * 1024 * 1024) throw std::runtime_error("native resource payload exceeds initial policy limit"); process.read_memory(data, resource.size); return data; } environment.set_last_error(6); return 0; } bool NativeResources::dispatch(std::string_view name) { uint64_t result = 0; if (name == "FindResourceA" || name == "FindResourceW" || name == "FindResourceExA" || name == "FindResourceExW") { const bool extended = name == "FindResourceExA" || name == "FindResourceExW"; const auto args = process.api_arguments(extended ? 4 : 3); result = find(args[0], args[extended ? 1 : 2], args[extended ? 2 : 1], static_cast(extended ? args[3] : 0), name.back() == 'W'); } else if (name == "LoadResource" || name == "SizeofResource") { const auto args = process.api_arguments(2); result = access(args[0], args[1], name == "SizeofResource"); } else if (name == "LockResource") { result = lock(process.api_arguments(1)[0]); } else { return false; } process.return_api(result); return true; } }