libredex/RedexResources.h (153 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem.hpp> #include <boost/filesystem/operations.hpp> #include <boost/noncopyable.hpp> #include <boost/optional.hpp> #include <map> #include <memory> #include <set> #include <string> #include <unordered_map> #include <unordered_set> #include <utility> #include <vector> #include "androidfw/ResourceTypes.h" #include "RedexMappedFile.h" const char* const ONCLICK_ATTRIBUTE = "android:onClick"; const char* const RES_DIRECTORY = "res"; const char* const OBFUSCATED_RES_DIRECTORY = "r"; const uint32_t PACKAGE_RESID_START = 0x7f000000; /* * These are all the components which may contain references to Java classes in * their attributes. */ enum class ComponentTag { Activity, ActivityAlias, Provider, Receiver, Service, }; /** * Indicate the value of the "exported" attribute of a component. */ enum class BooleanXMLAttribute { True, False, Undefined, }; // Populate the ComponentTagInfo list of authority class names void parse_authorities(const std::string& text, std::unordered_set<std::string>* authority_classes); struct ComponentTagInfo { ComponentTag tag; std::string classname; BooleanXMLAttribute is_exported; std::string permission; std::string protection_level; // Not defined on <provider> bool has_intent_filters{false}; // Only defined on <provider> std::unordered_set<std::string> authority_classes; ComponentTagInfo(ComponentTag tag, const std::string& classname, BooleanXMLAttribute is_exported, std::string permission, std::string protection_level) : tag(tag), classname(classname), is_exported(is_exported), permission(std::move(permission)), protection_level(std::move(protection_level)) {} }; struct ManifestClassInfo { std::unordered_set<std::string> application_classes; std::unordered_set<std::string> instrumentation_classes; std::vector<ComponentTagInfo> component_tags; }; /** * Indicates whether or not a file path is from the perspective of the zip file * input to Redex, or the file path as meant to be read on device. */ enum class ResourcePathType { ZipPath, DevicePath, }; class ResourceTableFile { public: virtual ~ResourceTableFile() {} virtual void collect_resid_values_and_hashes( const std::vector<uint32_t>& ids, std::map<size_t, std::vector<uint32_t>>* res_by_hash) = 0; virtual bool resource_value_identical(uint32_t a_id, uint32_t b_id) = 0; virtual std::unordered_set<uint32_t> get_types_by_name( const std::unordered_set<std::string>& type_names) = 0; virtual void delete_resource(uint32_t red_id) = 0; virtual void remap_res_ids_and_serialize( const std::vector<std::string>& resource_files, const std::map<uint32_t, uint32_t>& old_to_new) = 0; virtual void remap_file_paths_and_serialize( const std::vector<std::string>& resource_files, const std::unordered_map<std::string, std::string>& old_to_new) = 0; // Removes entries from string pool structures that are not referenced by // entries/values in the resource table virtual void remove_unreferenced_strings(); // Returns any file paths from entries in the given ID. A non-existent ID or // an for which all values are not files will return an empty vector. // NOTE: callers should be resilient against duplicate file paths being // returned, which could concievably exist. virtual std::vector<std::string> get_files_by_rid( uint32_t res_id, ResourcePathType path_type = ResourcePathType::DevicePath) = 0; // Follows the reference links for a resource for all configurations. Outputs // all the nodes visited, as well as strings that may be additional resource // file paths. virtual void walk_references_for_resource( uint32_t resID, ResourcePathType path_type, std::unordered_set<uint32_t>* nodes_visited, std::unordered_set<std::string>* potential_file_paths) = 0; // Return the resource ids based on the given resource name. std::vector<uint32_t> get_res_ids_by_name(const std::string& name) const { if (name_to_ids.count(name)) { return name_to_ids.at(name); } return std::vector<uint32_t>{}; } android::SortedVector<uint32_t> sorted_res_ids; std::map<uint32_t, std::string> id_to_name; std::map<std::string, std::vector<uint32_t>> name_to_ids; protected: ResourceTableFile() {} }; class AndroidResources { public: virtual boost::optional<int32_t> get_min_sdk() = 0; virtual ManifestClassInfo get_manifest_class_info() = 0; // Given the xml file name, return the list of resource ids referred in xml // attributes. virtual std::unordered_set<uint32_t> get_xml_reference_attributes( const std::string& filename) = 0; // Rewrites all tag names/attribute values that are in the given map, for // every non-raw XML file in the directory. void rename_classes_in_layouts( const std::map<std::string, std::string>& rename_map); // Iterates through all layouts in the given directory. Adds all class names // to the output set, and allows for any specified attribute values to be // returned as well. Attribute names should specify their namespace, if any // (so android:onClick instead of just onClick) void collect_layout_classes_and_attributes( const std::unordered_set<std::string>& attributes_to_read, std::unordered_set<std::string>* out_classes, std::unordered_multimap<std::string, std::string>* out_attributes); // Same as above, for single file. virtual void collect_layout_classes_and_attributes_for_file( const std::string& file_path, const std::unordered_set<std::string>& attributes_to_read, std::unordered_set<std::string>* out_classes, std::unordered_multimap<std::string, std::string>* out_attributes) = 0; virtual std::unique_ptr<ResourceTableFile> load_res_table() = 0; virtual size_t remap_xml_reference_attributes( const std::string& filename, const std::map<uint32_t, uint32_t>& kept_to_remapped_ids) = 0; virtual std::unordered_set<std::string> find_all_xml_files() = 0; virtual std::vector<std::string> find_resources_files() = 0; virtual std::string get_base_assets_dir() = 0; // Classnames present in native libraries (lib/*/*.so) std::unordered_set<std::string> get_native_classes(); const std::string& get_directory() { return m_directory; } virtual ~AndroidResources() {} protected: explicit AndroidResources(const std::string& directory) : m_directory(directory) {} virtual std::vector<std::string> find_res_directories() = 0; virtual std::vector<std::string> find_lib_directories() = 0; // Mutate the given file based on the rename map, returning whether or not it // worked with some potentially meaningless out params for size metrics. virtual bool rename_classes_in_layout( const std::string& file_path, const std::map<std::string, std::string>& rename_map, size_t* out_num_renamed) = 0; const std::string& m_directory; }; std::unique_ptr<AndroidResources> create_resource_reader( const std::string& directory); // For testing only! std::unordered_set<std::string> extract_classes_from_native_lib( const std::string& lib_contents); std::unordered_set<std::string> get_files_by_suffix( const std::string& directory, const std::string& suffix); std::unordered_set<std::string> get_xml_files(const std::string& directory); // Checks if the file is in a res/raw folder. Such a file won't be considered // for resource remapping, class name extraction, etc. These files don't follow // binary XML format, and thus are out of scope for many optimizations. bool is_raw_resource(const std::string& filename); // Convenience method for copying values in a multimap to a set, for a // particular key. std::set<std::string> multimap_values_to_set( const std::unordered_multimap<std::string, std::string>& map, const std::string& key); const int TYPE_INDEX_BIT_SHIFT = 16; const int PACKAGE_INDEX_BIT_SHIFT = 24; const uint32_t PACKAGE_MASK_BIT = 0xFF000000; const uint32_t TYPE_MASK_BIT = 0x00FF0000; const uint32_t ENTRY_MASK_BIT = 0x0000FFFF;