Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b0643302 authored by Luke Nicholson's avatar Luke Nicholson
Browse files

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
parent dcc0da28
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -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;
  }
+22 −6
Original line number Diff line number Diff line
@@ -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;
    }
+8 −0
Original line number Diff line number Diff line
@@ -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 {
+118 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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