Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix RemoveUninstantiablesPass in face of Proguard stripping #744

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions libredex/ConfigFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ ConfigFiles::ConfigFiles(const Json::Value& config, const std::string& outdir)
new ProguardMap(config.get("proguard_map", "").asString(),
config.get("use_new_rename_map", 0).asBool())),
m_printseeds(config.get("printseeds", "").asString()),
m_method_profiles(new method_profiles::MethodProfiles()),
m_secondary_method_profiles(new method_profiles::MethodProfiles()) {
m_method_profiles(new method_profiles::MethodProfiles(*m_proguard_map)),
m_secondary_method_profiles(new method_profiles::MethodProfiles(*m_proguard_map)) {

m_coldstart_class_filename = config.get("coldstart_classes", "").asString();
if (m_coldstart_class_filename.empty()) {
Expand All @@ -49,6 +49,11 @@ ConfigFiles::ConfigFiles(const Json::Value& config, const std::string& outdir)
m_instruction_size_bitwidth_limit = instruction_size_bitwidth_limit;
}

ConfigFiles::ConfigFiles(const Json::Value& config, std::istream& proguard_input) : ConfigFiles(config, "") {
m_proguard_map = std::make_unique<ProguardMap>(proguard_input);
m_method_profiles = std::make_unique<method_profiles::MethodProfiles>(*m_proguard_map);
}

ConfigFiles::ConfigFiles(const Json::Value& config) : ConfigFiles(config, "") {}

ConfigFiles::~ConfigFiles() {
Expand Down
2 changes: 2 additions & 0 deletions libredex/ConfigFiles.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class MethodProfiles;
*/
struct ConfigFiles {
explicit ConfigFiles(const Json::Value& config);
// For test purposes to inject a ProguardMap without a stand-alone file
explicit ConfigFiles(const Json::Value& config, std::istream& proguard_input);
ConfigFiles(const Json::Value& config, const std::string& outdir);
~ConfigFiles();

Expand Down
2 changes: 1 addition & 1 deletion libredex/DexHasher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ void Impl::hash(const IRCode* c) {

void Impl::hash(const cfg::ControlFlowGraph& cfg) {
hash(cfg.get_registers_size());
hash(cfg.entry_block()->id());
hash((uint64_t)cfg.entry_block()->id());
std::unordered_map<const MethodItemEntry*, uint32_t> mie_ids;
std::unordered_map<DexPosition*, uint32_t> pos_ids;
for (auto b : cfg.blocks()) {
Expand Down
3 changes: 2 additions & 1 deletion libredex/MethodProfiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ std::optional<MethodProfiles::ParsedMain> MethodProfiles::parse_main_internal(
case NAME:
// We move the string to a unique_ptr, so that its location is pinned, and
// the string_views of the mdt are defined.
result.ref_str = std::make_unique<std::string>(cell);
result.ref_str = std::make_unique<std::string>(
m_pg_map.translate_method(std::string(cell)));
result.mdt =
dex_member_refs::parse_method</*kCheckFormat=*/true>(*result.ref_str);
result.ref = DexMethod::get_method(*result.mdt);
Expand Down
5 changes: 4 additions & 1 deletion libredex/MethodProfiles.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <string_view>

#include "DexClass.h"
#include "ProguardMap.h"
#include "Timer.h"
#include "Trace.h"

Expand Down Expand Up @@ -60,7 +61,8 @@ const std::string COLD_START = "ColdStart";

class MethodProfiles {
public:
MethodProfiles() {}
MethodProfiles(): m_pg_map(std::move(ProguardMap())) {}
MethodProfiles(const ProguardMap& pg_map): m_pg_map(pg_map) {}

void initialize(const std::vector<std::string>& csv_filenames) {
m_initialized = true;
Expand Down Expand Up @@ -136,6 +138,7 @@ class MethodProfiles {

private:
static AccumulatingTimer s_process_unresolved_lines_timer;
const ProguardMap& m_pg_map;
AllInteractions m_method_stats;
// Resolution may fail because of renaming or generated methods. Store the
// unresolved lines here (per interaction) so we can update after passes run
Expand Down
167 changes: 161 additions & 6 deletions opt/remove-uninstantiables/RemoveUninstantiablesPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@
#include "MethodDedup.h"
#include "NullPointerExceptionUtil.h"
#include "PassManager.h"
#include "ProguardMap.h"
#include "Resolver.h"
#include "Show.h"
#include "Trace.h"
#include "Walkers.h"

#include <boost/optional.hpp>
#include <iostream>

std::string RemoveUninstantiablesPass::m_proguard_usage_name = "";
std::istream* RemoveUninstantiablesPass::test_only_usage_file_input = nullptr;

namespace {

Expand Down Expand Up @@ -280,6 +285,126 @@ void RemoveUninstantiablesPass::Stats::report(PassManager& mgr) const {
#undef REPORT
}

// Map deobfuscated type name in internal format (La/b/MyClass;) to the obfuscated DexType*.
std::unordered_map<std::string_view, DexType*> make_deobfuscated_map(const std::unordered_set<DexType*>& obfuscated_types) {
std::unordered_map<std::string_view, DexType*> deobfuscated_to_type;
for (auto obfuscated_type : obfuscated_types) {
auto obfuscated_cls = type_class(obfuscated_type);
auto deobfuscated_name = obfuscated_cls->get_deobfuscated_name().str();
TRACE(
RMUNINST, 4, "Mapping deobfuscated name to obfuscated type: '%s' -> '%s'",
deobfuscated_name.data(), SHOW(obfuscated_type));
if (deobfuscated_to_type.count(deobfuscated_name) > 0) {
always_assert_log(
deobfuscated_to_type.count(deobfuscated_name),
"Multiple uninstantiable types with same deobfuscated name! '%s' == '%s' && '%s'",
deobfuscated_name.data(),
SHOW(obfuscated_type),
SHOW(deobfuscated_to_type.at(deobfuscated_name))
);
}
deobfuscated_to_type.emplace(deobfuscated_name, obfuscated_type);
}
return std::move(deobfuscated_to_type);
}

// Handles a single line of usage.txt proguard file from `-printusage`.
// Focuses on looking for removed constructors for instantiability.
//
// Typical usage.txt contents:
// ----
// androidx.core.view.ViewKt$postOnAnimationDelayed$runnable$1
// androidx.core.view.ViewParentCompat:
// private static int[] sTempNestedScrollConsumed
// 41:41:private ViewParentCompat()
// ----
// A classname on its own line means the whole class was removed and can be
// ignored.
// If the classname is followed by a ":", then what follows is a 4-space
// indented list of parts that were removed. The list continues until we find a
// de-indent which means we once again have a class name.
// We specifically look for removed constructors by doing a name check for a
// removed method that is named the name of the class. All names are
// unobfuscated (pre-proguard) and need to be obfuscated to find relevant redex
// classes.
void UsageHandler::handle_usage_line(
const std::string& line,
const std::unordered_map<std::string_view, DexType*>& deobfuscated_to_uninstantiable_type,
std::unordered_set<DexType*>& uninstantiable_types) {
TRACE(RMUNINST, 5, "usage.txt line: %s", line.c_str());
// The current line is a class
if (line.find(" ") != 0) {
int dot_index = line.find_last_of('.');
int end_index = line.find_first_of(':');
cls_name = line.substr(dot_index + 1, end_index - dot_index - 1);
// usage lines use external naming (a.b.MyClass) and we need to translate
// to internal naming (La/b/MyClass;) to match type names
type_name = convert_type(line.substr(0, end_index));
usage_type = DexType::get_type(type_name);

TRACE(RMUNINST, 5, "usage.txt line type (deobfuscated) name: '%s'", type_name.c_str());
if (usage_type != nullptr) {
TRACE(RMUNINST, 5, "usage.txt line type (deobfuscated): '%s'", SHOW(usage_type));
} else {
TRACE(RMUNINST, 5, "usage.txt line type (deobfuscated) does not exist!");
}

if (deobfuscated_to_uninstantiable_type.count(type_name) <= 0) {
TRACE(RMUNINST, 5, "usage.txt type has no obfuscated version: %s", type_name.c_str());
cls_type = usage_type;
} else {
cls_type = deobfuscated_to_uninstantiable_type.at(type_name);
TRACE(RMUNINST, 5, "usage.txt obfuscated type: '%s' -> '%s'", SHOW(cls_type), type_name.c_str());
}
if (uninstantiable_types.count(cls_type)) {
has_ctor = false;
} else {
has_ctor = true;
}
} else {
if (has_ctor) {
return;
}
// The current line is a constructor method
if (line.find(cls_name + "(") != std::string::npos) {
has_ctor = true;
if (uninstantiable_types.count(cls_type)) {
TRACE(
RMUNINST, 4,
"Keeping class due to proguard-removed constructor: %s",
SHOW(cls_type));
count++;
} else {
TRACE(
RMUNINST, 4, "Keep class not present! Skipping: %s",
SHOW(cls_type));
}
uninstantiable_types.erase(cls_type);
}
}
}

// Prune the list of possibly uninstantiable types by looking at the <init>
// constructor that Proguard deleted.
void RemoveUninstantiablesPass::readUsage(std::istream& usage_file, std::unordered_set<DexType*>& uninstantiable_types) {
// uninstantiable_types may have obfuscated names. Deobfuscate for usage.txt.
std::unordered_map<std::string_view, DexType*> deobfuscated_to_uninstantiable_type =
make_deobfuscated_map(uninstantiable_types);
TRACE(
RMUNINST, 1, "Mapped %lu deobfuscated<->obfuscated types",
deobfuscated_to_uninstantiable_type.size());

UsageHandler uh;
std::string line;
while(getline(usage_file, line)) {
uh.handle_usage_line(line, deobfuscated_to_uninstantiable_type, uninstantiable_types);
}
TRACE(
RMUNINST, 1,
"Removed %u types from uninstantiable_types by reading proguard file",
uh.count);
}

// Computes set of uninstantiable types, also looking at the type system to
// find non-external (and non-native)...
// - interfaces that are not annotations, are not root (or unrenameable) and
Expand Down Expand Up @@ -317,13 +442,24 @@ RemoveUninstantiablesPass::compute_scoped_uninstantiable_types(
instantiable_classes.insert(cls);
}
});

if(RemoveUninstantiablesPass::m_proguard_usage_name.compare("") != 0) {
std::ifstream infile;
infile.open(RemoveUninstantiablesPass::m_proguard_usage_name.data());
always_assert(infile.is_open());
readUsage(infile, uninstantiable_types);
infile.close();
} else if (RemoveUninstantiablesPass::test_only_usage_file_input != nullptr) {
readUsage(*RemoveUninstantiablesPass::test_only_usage_file_input, uninstantiable_types);
}

// Next, we prune the list of possibly uninstantiable types by looking at
// what instantiable classes implement and extend.
std::unordered_set<const DexClass*> visited;
std::function<bool(const DexClass*)> visit;
std::function<void(const DexClass*)> visit;
visit = [&](const DexClass* cls) {
if (cls == nullptr || !visited.insert(cls).second) {
return false;
return;
}
if (instantiable_children) {
(*instantiable_children)[cls->get_super_class()].insert(cls->get_type());
Expand All @@ -332,13 +468,17 @@ RemoveUninstantiablesPass::compute_scoped_uninstantiable_types(
for (auto interface : *cls->get_interfaces()) {
visit(type_class(interface));
}
return true;
visit(type_class(cls->get_super_class()));
};

size_t count = uninstantiable_types.size();
for (auto cls : instantiable_classes) {
while (visit(cls)) {
cls = type_class(cls->get_super_class());
}
visit(cls);
}

TRACE(
RMUNINST, 1, "Removed %lu classes by pruning parents/interfaces",
count - uninstantiable_types.size());
uninstantiable_types.insert(type::java_lang_Void());
return uninstantiable_types;
}
Expand Down Expand Up @@ -445,6 +585,21 @@ void RemoveUninstantiablesPass::run_pass(DexStoresVector& stores,
instantiable_children;
std::unordered_set<DexType*> scoped_uninstantiable_types =
compute_scoped_uninstantiable_types(scope, &instantiable_children);
TRACE(RMUNINST, 2, "Total instantiable types: %lu", instantiable_children.size());
TRACE(RMUNINST, 2, "Total uninstantiable types: %lu", scoped_uninstantiable_types.size());
for (auto utype : scoped_uninstantiable_types) {
TRACE(
RMUNINST, 5, "uninstantiable class: %s -> %s",
SHOW(utype), type_class(utype)->get_deobfuscated_name_or_empty().data());
}
for (auto itype : instantiable_children) {
if (itype.first == nullptr) {
continue;
}
TRACE(
RMUNINST, 5, "instantible class: %s -> %s",
SHOW(itype.first), type_class(itype.first)->get_deobfuscated_name_or_empty().data());
}
OverriddenVirtualScopesAnalysis overridden_virtual_scopes_analysis(
scope, scoped_uninstantiable_types, instantiable_children);
// We perform structural changes, i.e. whether a method has a body and
Expand Down
42 changes: 42 additions & 0 deletions opt/remove-uninstantiables/RemoveUninstantiablesPass.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,34 @@ namespace cfg {
class ControlFlowGraph;
} // namespace cfg

std::unordered_map<std::string_view, DexType*> make_deobfuscated_map(const std::unordered_set<DexType*>& obfuscated_types);

class UsageHandler {
public:
std::string cls_name;
std::string type_name;
DexType* cls_type = nullptr;
DexType* usage_type = nullptr;
int count = 0;
bool has_ctor = false;

void reset() {
cls_name = "";
type_name = "";
cls_type = nullptr;
usage_type = nullptr;
count = 0;
has_ctor = false;
};

UsageHandler() {};

void handle_usage_line(
const std::string& line,
const std::unordered_map<std::string_view, DexType*>& deobfuscated_uninstantiable_type,
std::unordered_set<DexType*>& uninstantiable_types);
};

/// Looks for mentions of classes that have no constructors and use the fact
/// they can't be instantiated to simplify those mentions:
///
Expand All @@ -33,6 +61,10 @@ class ControlFlowGraph;
/// `null`.
class RemoveUninstantiablesPass : public Pass {
public:
UsageHandler uh;
// Testing injector to mimic m_proguard_usage_name
static std::istream* test_only_usage_file_input;

RemoveUninstantiablesPass() : Pass("RemoveUninstantiablesPass") {}

/// Counts of references to uninstantiable classes removed.
Expand Down Expand Up @@ -60,6 +92,8 @@ class RemoveUninstantiablesPass : public Pass {
std::unordered_map<DexType*, std::unordered_set<DexType*>>*
instantiable_children = nullptr);

static void readUsage(std::istream& usage_file, std::unordered_set<DexType*>& uninstantiable_types);

/// Look for mentions of uninstantiable classes in \p cfg and modify them
/// in-place.
static Stats replace_uninstantiable_refs(
Expand All @@ -73,4 +107,12 @@ class RemoveUninstantiablesPass : public Pass {
static Stats replace_all_with_throw(cfg::ControlFlowGraph& cfg);

void run_pass(DexStoresVector&, ConfigFiles&, PassManager&) override;

void bind_config() override {
// The Proguard usage.txt file name. Need to be added by gradle plugin.
bind("proguard_usage_name", "", m_proguard_usage_name);
}

protected:
static std::string m_proguard_usage_name;
};
Loading