diff options
| author | 2017-12-01 15:29:03 -0800 | |
|---|---|---|
| committer | 2017-12-04 17:54:05 -0800 | |
| commit | b0643302c78d00db3036707a0df0ae5ce923ae15 (patch) | |
| tree | a77389c644eff8b5ef167c8e88f831a65a73e739 | |
| parent | dcc0da281b4a7e250a732da524f90cbb954516eb (diff) | |
Enable obfuscation of resource names, with whitelisting support.
Test: Built aapt2, ran optimize on gmail apk with sample whitelist
config file, and flags enabled. Added two unit tests to TableFlattener
covering obfuscation logic.
Change-Id: Iad6329d75ff440121bf1a2cdf09c5f4bf4199d9d
| -rw-r--r-- | tools/aapt2/cmd/Optimize.cpp | 33 | ||||
| -rw-r--r-- | tools/aapt2/format/binary/TableFlattener.cpp | 28 | ||||
| -rw-r--r-- | tools/aapt2/format/binary/TableFlattener.h | 8 | ||||
| -rw-r--r-- | tools/aapt2/format/binary/TableFlattener_test.cpp | 118 | ||||
| -rw-r--r-- | tools/aapt2/text/Unicode_test.cpp | 1 |
5 files changed, 182 insertions, 6 deletions
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 2bf91a530526..3b90637d9629 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -17,6 +17,7 @@ #include <memory> #include <vector> +#include "android-base/file.h" #include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" @@ -47,6 +48,7 @@ using ::aapt::configuration::Artifact; using ::aapt::configuration::PostProcessingConfiguration; using ::android::ResTable_config; using ::android::StringPiece; +using ::android::base::ReadFileToString; using ::android::base::StringAppendF; using ::android::base::StringPrintf; @@ -279,6 +281,20 @@ class OptimizeCommand { OptimizeContext* context_; }; +bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context, + OptimizeOptions* options) { + std::string contents; + if (!ReadFileToString(path, &contents, true)) { + context->GetDiagnostics()->Error(DiagMessage() + << "failed to parse whitelist from config file: " << path); + return false; + } + for (const StringPiece& resource_name : util::Tokenize(contents, ',')) { + options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string()); + } + return true; +} + bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, OptimizeOptions* out_options) { const xml::XmlResource* manifest = apk->GetManifest(); @@ -302,6 +318,7 @@ int Optimize(const std::vector<StringPiece>& args) { OptimizeContext context; OptimizeOptions options; Maybe<std::string> config_path; + Maybe<std::string> whitelist_path; Maybe<std::string> target_densities; Maybe<std::string> target_abis; std::vector<std::string> configs; @@ -320,6 +337,10 @@ int Optimize(const std::vector<StringPiece>& args) { "All the resources that would be unused on devices of the given densities will be \n" "removed from the APK.", &target_densities) + .OptionalFlag("--whitelist-config-path", + "Path to the whitelist.cfg file containing whitelisted resources \n" + "whose names should not be altered in final resource tables.", + &whitelist_path) .OptionalFlag( "--target-abis", "Comma separated list of the CPU ABIs that the APK will be optimized for.\n" @@ -339,6 +360,9 @@ int Optimize(const std::vector<StringPiece>& args) { "Enables encoding sparse entries using a binary search tree.\n" "This decreases APK size at the cost of resource retrieval performance.", &options.table_flattener_options.use_sparse_entries) + .OptionalSwitch("--enable-resource-obfuscation", + "Enables obfuscation of key string pool to single value", + &options.table_flattener_options.collapse_key_stringpool) .OptionalSwitch("-v", "Enables verbose logging", &verbose); if (!flags.Parse("aapt2 optimize", args, &std::cerr)) { @@ -425,6 +449,15 @@ int Optimize(const std::vector<StringPiece>& args) { return 1; } + if (options.table_flattener_options.collapse_key_stringpool) { + if (whitelist_path) { + std::string& path = whitelist_path.value(); + if (!ExtractWhitelistFromConfig(path, &context, &options)) { + return 1; + } + } + } + if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) { return 1; } diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 2a51df3912e3..a3034df91d82 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -220,12 +220,15 @@ class MapFlattenVisitor : public ValueVisitor { class PackageFlattener { public: PackageFlattener(IAaptContext* context, ResourceTablePackage* package, - const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries) + const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries, + bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources) : context_(context), diag_(context->GetDiagnostics()), package_(package), shared_libs_(shared_libs), - use_sparse_entries_(use_sparse_entries) { + use_sparse_entries_(use_sparse_entries), + collapse_key_stringpool_(collapse_key_stringpool), + whitelisted_resources_(whitelisted_resources) { } bool FlattenPackage(BigBuffer* buffer) { @@ -494,13 +497,23 @@ class PackageFlattener { // configuration available. Here we reverse this to match the binary // table. std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map; - for (ResourceEntry* entry : sorted_entries) { - const uint32_t key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); + // hardcoded string uses characters which make it an invalid resource name + const std::string obfuscated_resource_name = "0_resource_name_obfuscated"; + + for (ResourceEntry* entry : sorted_entries) { + uint32_t local_key_index; + if (!collapse_key_stringpool_ || + whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) { + local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); + } else { + // resource isn't whitelisted, add it as obfuscated value + local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); + } // Group values by configuration. for (auto& config_value : entry->values) { config_to_entry_list_map[config_value->config].push_back( - FlatEntry{entry, config_value->value.get(), key_index}); + FlatEntry{entry, config_value->value.get(), local_key_index}); } } @@ -549,6 +562,8 @@ class PackageFlattener { bool use_sparse_entries_; StringPool type_pool_; StringPool key_pool_; + bool collapse_key_stringpool_; + const std::set<std::string>& whitelisted_resources_; }; } // namespace @@ -593,7 +608,8 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { } PackageFlattener flattener(context, package.get(), &table->included_packages_, - options_.use_sparse_entries); + options_.use_sparse_entries, options_.collapse_key_stringpool, + options_.whitelisted_resources); if (!flattener.FlattenPackage(&package_buffer)) { return false; } diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 88cbddfd14ab..c2e1d4b4ce8c 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -35,6 +35,14 @@ struct TableFlattenerOptions { // This is only available on platforms O+ and will only be respected when // minSdk is O+. bool use_sparse_entries = false; + + // When true, the key string pool in the final ResTable + // is collapsed to a single entry. All resource entries + // have name indices that point to this single value + bool collapse_key_stringpool = false; + + // Set of whitelisted resource names to avoid altering in key stringpool + std::set<std::string> whitelisted_resources; }; class TableFlattener : public IResourceTableConsumer { diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index e11890bbd078..f0b80d22a9b4 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -127,6 +127,15 @@ class TableFlattenerTest : public ::testing::Test { << StringPiece16(actual_name.name, actual_name.nameLen) << "'"; } + ResourceName actual_res_name(resName.value()); + + if (expected_res_name.entry != actual_res_name.entry || + expected_res_name.package != actual_res_name.package || + expected_res_name.type != actual_res_name.type) { + return ::testing::AssertionFailure() << "expected resource '" << expected_res_name.to_string() + << "' but got '" << actual_res_name.to_string() << "'"; + } + if (expected_config != config) { return ::testing::AssertionFailure() << "expected config '" << expected_config << "' but got '" << ConfigDescription(config) << "'"; @@ -450,4 +459,113 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) { ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result)); } +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) + .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) + .AddValue("com.app.test:id/three", ResourceId(0x7f020002), + test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) + .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") + .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + + ResTable res_table; + + ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, + 2u, ResTable_config::CONFIG_VERSION)); + + std::u16string foo_str = u"foo"; + ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", + ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + + std::u16string bar_path = u"res/layout/bar.xml"; + idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); +} + +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) + .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) + .AddValue("com.app.test:id/three", ResourceId(0x7f020002), + test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) + .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo") + .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + options.whitelisted_resources.insert("test"); + options.whitelisted_resources.insert("three"); + ResTable res_table; + + ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {}, + Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, + 2u, ResTable_config::CONFIG_VERSION)); + + std::u16string foo_str = u"foo"; + ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {}, + Res_value::TYPE_STRING, (uint32_t)idx, 0u)); + + std::u16string bar_path = u"res/layout/bar.xml"; + idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); +} + } // namespace aapt diff --git a/tools/aapt2/text/Unicode_test.cpp b/tools/aapt2/text/Unicode_test.cpp index a8e797cf3d17..16bc2e84224a 100644 --- a/tools/aapt2/text/Unicode_test.cpp +++ b/tools/aapt2/text/Unicode_test.cpp @@ -63,6 +63,7 @@ TEST(UnicodeTest, IsValidResourceEntryName) { EXPECT_FALSE(IsValidResourceEntryName("Føø/Bar")); EXPECT_FALSE(IsValidResourceEntryName("Føø:Bar")); EXPECT_FALSE(IsValidResourceEntryName("Føø;Bar")); + EXPECT_FALSE(IsValidResourceEntryName("0_resource_name_obfuscated")); } } // namespace text |