libredex/Creators.cpp (658 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. */ #include "Creators.h" #include <boost/range/adaptor/reversed.hpp> #include "DexPosition.h" #include "IROpcode.h" #include "RedexContext.h" #include "Show.h" #include "Transform.h" namespace { // TODO: make naming of methods smart const DexString* get_name(DexMethod* meth) { std::string name = "__st__" + meth->get_name()->str(); return DexString::make_string(name); } DexProto* make_static_sig(DexMethod* meth) { auto proto = meth->get_proto(); auto rtype = proto->get_rtype(); DexTypeList::ContainerType arg_list; arg_list.push_back(meth->get_class()); auto* args = proto->get_args(); arg_list.insert(arg_list.end(), args->begin(), args->end()); auto new_args = DexTypeList::make_type_list(std::move(arg_list)); return DexProto::make_proto(rtype, new_args); } } // namespace std::string ClassCreator::show_cls(const DexClass* cls) { return show(cls); } std::string ClassCreator::show_type(const DexType* type) { return show(type); } DexClass* ClassCreator::create() { always_assert_log(m_cls->m_self, "Self cannot be null in a DexClass"); if (m_cls->m_super_class == nullptr) { if (m_cls->m_self != type::java_lang_Object()) { always_assert_log(m_cls->m_super_class, "No supertype found for %s", show_type(m_cls->m_self).c_str()); } } m_cls->m_interfaces = DexTypeList::make_type_list(std::move(m_interfaces)); g_redex->publish_class(m_cls); return m_cls; } MethodBlock::MethodBlock(const IRList::iterator& iterator, MethodCreator* creator) : mc(creator), curr(iterator) { mc->blocks.push_back(std::unique_ptr<MethodBlock>(this)); } void MethodBlock::invoke(DexMethod* meth, const std::vector<Location>& args) { always_assert(meth->is_concrete()); IROpcode opcode = opcode::invoke_for_method(meth); invoke(opcode, meth, args); } void MethodBlock::invoke(IROpcode opcode, DexMethodRef* meth, const std::vector<Location>& args) { always_assert(opcode::is_an_invoke(opcode)); auto invk = new IRInstruction(opcode); uint16_t arg_count = static_cast<uint16_t>(args.size()); invk->set_method(meth)->set_srcs_size(arg_count); for (uint16_t i = 0; i < arg_count; i++) { auto arg = args.at(i); invk->set_src(i, arg.get_reg()); } push_instruction(invk); } void MethodBlock::new_instance(DexType* type, Location& dst) { auto insn = new IRInstruction(OPCODE_NEW_INSTANCE); insn->set_type(type); push_instruction(insn); push_instruction((new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT)) ->set_dest(dst.get_reg())); } void MethodBlock::new_array(DexType* type, const Location& size, const Location& dst) { auto insn = new IRInstruction(OPCODE_NEW_ARRAY); insn->set_type(type); insn->set_srcs_size(1); insn->set_src(0, size.get_reg()); push_instruction(insn); push_instruction((new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT)) ->set_dest(dst.get_reg())); } void MethodBlock::throwex(Location ex) { auto insn = new IRInstruction(OPCODE_THROW); insn->set_src(0, ex.get_reg()); push_instruction(insn); } void MethodBlock::iget(DexField* field, Location obj, Location& dst) { always_assert(field->is_concrete() && !is_static(field)); IROpcode opcode; char t = type::type_shorty(field->get_type()); switch (t) { case 'Z': opcode = OPCODE_IGET_BOOLEAN; break; case 'B': opcode = OPCODE_IGET_BYTE; break; case 'S': opcode = OPCODE_IGET_SHORT; break; case 'C': opcode = OPCODE_IGET_CHAR; break; case 'I': case 'F': opcode = OPCODE_IGET; break; case 'J': case 'D': opcode = OPCODE_IGET_WIDE; break; case 'L': case '[': opcode = OPCODE_IGET_OBJECT; break; default: not_reached(); } ifield_op(opcode, field, obj, dst); } void MethodBlock::iput(DexField* field, Location obj, Location src) { always_assert(field->is_concrete() && !is_static(field)); IROpcode opcode; char t = type::type_shorty(field->get_type()); switch (t) { case 'Z': opcode = OPCODE_IPUT_BOOLEAN; break; case 'B': opcode = OPCODE_IPUT_BYTE; break; case 'S': opcode = OPCODE_IPUT_SHORT; break; case 'C': opcode = OPCODE_IPUT_CHAR; break; case 'I': case 'F': opcode = OPCODE_IPUT; break; case 'J': case 'D': opcode = OPCODE_IPUT_WIDE; break; case 'L': case '[': opcode = OPCODE_IPUT_OBJECT; break; default: not_reached(); } ifield_op(opcode, field, obj, src); } void MethodBlock::ifield_op(IROpcode opcode, DexField* field, Location obj, Location& src_or_dst) { always_assert(opcode::is_an_ifield_op(opcode)); if (opcode::is_an_iget(opcode)) { auto iget = new IRInstruction(opcode); iget->set_field(field); src_or_dst.type = field->get_type(); iget->set_src(0, obj.get_reg()); push_instruction(iget); push_instruction( (new IRInstruction(opcode::move_result_pseudo_for_iget(opcode))) ->set_dest(src_or_dst.get_reg())); } else { auto iput = new IRInstruction(opcode); iput->set_field(field); iput->set_src(0, src_or_dst.get_reg()); iput->set_src(1, obj.get_reg()); push_instruction(iput); } } void MethodBlock::sget(DexField* field, Location& dst) { always_assert(field->is_concrete() && is_static(field)); IROpcode opcode; char t = type::type_shorty(field->get_type()); switch (t) { case 'Z': opcode = OPCODE_SGET_BOOLEAN; break; case 'B': opcode = OPCODE_SGET_BYTE; break; case 'S': opcode = OPCODE_SGET_SHORT; break; case 'C': opcode = OPCODE_SGET_CHAR; break; case 'I': case 'F': opcode = OPCODE_SGET; break; case 'J': case 'D': opcode = OPCODE_SGET_WIDE; break; case 'L': case '[': opcode = OPCODE_SGET_OBJECT; break; default: not_reached(); } sfield_op(opcode, field, dst); } void MethodBlock::sput(DexField* field, Location src) { always_assert(field->is_concrete() && is_static(field)); IROpcode opcode; char t = type::type_shorty(field->get_type()); switch (t) { case 'Z': opcode = OPCODE_SPUT_BOOLEAN; break; case 'B': opcode = OPCODE_SPUT_BYTE; break; case 'S': opcode = OPCODE_SPUT_SHORT; break; case 'C': opcode = OPCODE_SPUT_CHAR; break; case 'I': case 'F': opcode = OPCODE_SPUT; break; case 'J': case 'D': opcode = OPCODE_SPUT_WIDE; break; case 'L': case '[': opcode = OPCODE_SPUT_OBJECT; break; default: not_reached(); } sfield_op(opcode, field, src); } void MethodBlock::sfield_op(IROpcode opcode, DexField* field, Location& src_or_dst) { always_assert(opcode::is_an_sfield_op(opcode)); if (opcode::is_an_sget(opcode)) { auto sget = new IRInstruction(opcode); sget->set_field(field); src_or_dst.type = field->get_type(); push_instruction(sget); push_instruction( (new IRInstruction(opcode::move_result_pseudo_for_sget(opcode))) ->set_dest(src_or_dst.get_reg())); } else { auto sput = new IRInstruction(opcode); sput->set_field(field)->set_src(0, src_or_dst.get_reg()); push_instruction(sput); } } void MethodBlock::move(Location src, Location& dst) { always_assert(src.is_compatible(dst.type)); auto ch = type::type_shorty(dst.type); redex_assert(ch != 'V'); IROpcode opcode; if (ch == 'L') opcode = OPCODE_MOVE_OBJECT; else if (ch == 'J' || ch == 'D') opcode = OPCODE_MOVE_WIDE; else opcode = OPCODE_MOVE; IRInstruction* move = new IRInstruction(opcode); move->set_dest(dst.get_reg()); move->set_src(0, src.get_reg()); dst.type = src.type; push_instruction(move); } void MethodBlock::move_result(Location& dst, DexType* type) { always_assert(dst.is_compatible(type)); auto ch = type::type_shorty(type); redex_assert(ch != 'V'); IROpcode opcode; if (ch == 'L') opcode = OPCODE_MOVE_RESULT_OBJECT; else if (ch == 'J' || ch == 'D') opcode = OPCODE_MOVE_RESULT_WIDE; else opcode = OPCODE_MOVE_RESULT; IRInstruction* mov_res = new IRInstruction(opcode); mov_res->set_dest(dst.get_reg()); dst.type = type; push_instruction(mov_res); } void MethodBlock::check_cast(Location& src_and_dst, DexType* type) { IRInstruction* check_cast = new IRInstruction(OPCODE_CHECK_CAST); auto reg = src_and_dst.get_reg(); check_cast->set_type(type)->set_src(0, reg); push_instruction(check_cast); push_instruction( (new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT))->set_dest(reg)); } void MethodBlock::instance_of(Location& obj, Location& dst, DexType* type) { always_assert(obj.is_ref()); always_assert(dst.type == type::_boolean()); IRInstruction* insn = new IRInstruction(OPCODE_INSTANCE_OF); insn->set_src(0, obj.get_reg()); insn->set_type(type); push_instruction(insn); push_instruction( (new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO))->set_dest(dst.get_reg())); } void MethodBlock::ret(Location loc) { auto ch = type::type_shorty(loc.type); redex_assert(ch != 'V'); IROpcode opcode; if (ch == 'L') opcode = OPCODE_RETURN_OBJECT; else if (ch == 'J' || ch == 'D') opcode = OPCODE_RETURN_WIDE; else opcode = OPCODE_RETURN; auto ret = new IRInstruction(opcode); ret->set_src(0, loc.get_reg()); push_instruction(ret); } void MethodBlock::ret_void() { push_instruction(new IRInstruction(OPCODE_RETURN_VOID)); } void MethodBlock::ret(DexType* rtype, Location loc) { if (rtype != type::_void()) { ret(loc); } else { ret_void(); } } void MethodBlock::load_const(Location& loc, int32_t value) { always_assert(!loc.is_wide()); IRInstruction* load = new IRInstruction(OPCODE_CONST); load->set_dest(loc.get_reg()); load->set_literal(value); loc.type = type::_int(); push_instruction(load); } void MethodBlock::load_const(Location& loc, int64_t value, DexType* type) { always_assert(type::is_wide_type(type) == loc.is_wide()); IRInstruction* load = new IRInstruction(loc.is_wide() ? OPCODE_CONST_WIDE : OPCODE_CONST); load->set_dest(loc.get_reg()); load->set_literal(value); loc.type = type; push_instruction(load); } void MethodBlock::load_const(Location& loc, double value) { always_assert(loc.is_wide()); IRInstruction* load = new IRInstruction(OPCODE_CONST_WIDE); load->set_dest(loc.get_reg()); load->set_literal(value); loc.type = type::_double(); push_instruction(load); } void MethodBlock::load_const(Location& loc, const DexString* value) { always_assert(!loc.is_wide()); IRInstruction* load = new IRInstruction(OPCODE_CONST_STRING); load->set_string(value); push_instruction(load); IRInstruction* move_result_pseudo = new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT); loc.type = type::java_lang_String(); move_result_pseudo->set_dest(loc.get_reg()); push_instruction(move_result_pseudo); } void MethodBlock::load_const(Location& loc, DexType* value) { always_assert(!loc.is_wide()); IRInstruction* load = new IRInstruction(OPCODE_CONST_CLASS); load->set_type(value); push_instruction(load); IRInstruction* move_result_pseudo = new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT); loc.type = type::java_lang_Class(); move_result_pseudo->set_dest(loc.get_reg()); push_instruction(move_result_pseudo); } void MethodBlock::load_null(Location& loc) { always_assert(!loc.is_wide()); IRInstruction* load = new IRInstruction(OPCODE_CONST); load->set_dest(loc.get_reg()); load->set_literal(0); loc.type = type::java_lang_Object(); push_instruction(load); } void MethodBlock::init_loc(Location& loc) { if (loc.is_ref()) { load_null(loc); } else if (loc.is_wide()) { load_const(loc, 0.0); } else { load_const(loc, 0); } } void MethodBlock::binop_lit16(IROpcode op, const Location& dest, const Location& src, int16_t literal) { always_assert(OPCODE_ADD_INT_LIT16 <= op && op <= OPCODE_XOR_INT_LIT16); always_assert(dest.type == src.type); always_assert(dest.type == type::_int()); IRInstruction* insn = new IRInstruction(op); insn->set_dest(dest.get_reg()); insn->set_src(0, src.get_reg()); insn->set_literal(literal); push_instruction(insn); } void MethodBlock::binop_lit8(IROpcode op, const Location& dest, const Location& src, int8_t literal) { always_assert(OPCODE_ADD_INT_LIT8 <= op && op <= OPCODE_USHR_INT_LIT8); always_assert(dest.type == src.type); always_assert(dest.type == type::_int()); IRInstruction* insn = new IRInstruction(op); insn->set_dest(dest.get_reg()); insn->set_src(0, src.get_reg()); insn->set_literal(literal); push_instruction(insn); } MethodBlock* MethodBlock::if_test(IROpcode if_op, Location first, Location second) { always_assert(OPCODE_IF_EQ <= if_op && if_op <= OPCODE_IF_LE); IRInstruction* op = new IRInstruction(if_op); op->set_src(0, first.get_reg()); op->set_src(1, second.get_reg()); return make_if_block(op); } MethodBlock* MethodBlock::if_testz(IROpcode if_op, Location test) { always_assert(OPCODE_IF_EQZ <= if_op && if_op <= OPCODE_IF_LEZ); IRInstruction* op = new IRInstruction(if_op); op->set_src(0, test.get_reg()); return make_if_block(op); } MethodBlock* MethodBlock::if_else_test(IROpcode if_op, Location first, Location second, MethodBlock** true_block) { always_assert(OPCODE_IF_EQ <= if_op && if_op <= OPCODE_IF_LE); IRInstruction* op = new IRInstruction(if_op); op->set_src(0, first.get_reg()); op->set_src(1, second.get_reg()); return make_if_else_block(op, true_block); } MethodBlock* MethodBlock::if_else_testz(IROpcode if_op, Location test, MethodBlock** true_block) { always_assert(OPCODE_IF_EQZ <= if_op && if_op <= OPCODE_IF_LEZ); IRInstruction* op = new IRInstruction(if_op); op->set_src(0, test.get_reg()); return make_if_else_block(op, true_block); } MethodBlock* MethodBlock::switch_op(Location test, std::map<int, MethodBlock*>& cases) { auto sw_opcode = new IRInstruction(OPCODE_SWITCH); sw_opcode->set_src(0, test.get_reg()); // Convert to SwitchIndices map. std::map<SwitchIndices, MethodBlock*> indices_cases; for (auto it : cases) { SwitchIndices indices = {it.first}; indices_cases[indices] = it.second; } auto mb = make_switch_block(sw_opcode, indices_cases); // Copy initialized case blocks back. for (const auto& it : indices_cases) { SwitchIndices indices = it.first; always_assert(indices.size()); int idx = *indices.begin(); cases[idx] = it.second; } return mb; } MethodBlock* MethodBlock::switch_op( Location test, std::map<SwitchIndices, MethodBlock*>& cases) { auto sw_opcode = new IRInstruction(OPCODE_SWITCH); sw_opcode->set_src(0, test.get_reg()); return make_switch_block(sw_opcode, cases); } void MethodCreator::load_locals(DexMethod* meth) { auto ii = InstructionIterable(meth->get_code()->get_param_instructions()); auto it = ii.begin(); if (!is_static(meth)) { make_local_at(meth->get_class(), it->insn->dest()); ++it; } auto proto = meth->get_proto(); auto args = proto->get_args(); if (args) { for (auto arg : *args) { make_local_at(arg, it->insn->dest()); ++it; } } always_assert(it == ii.end()); } void MethodBlock::push_instruction(IRInstruction* insn) { curr = mc->push_instruction(curr, insn); } IRList::iterator MethodCreator::push_instruction(const IRList::iterator& curr, IRInstruction* insn) { if (curr == meth_code->end()) { meth_code->push_back(insn); return std::prev(meth_code->end()); } else { return meth_code->insert_after(curr, insn); } } MethodBlock* MethodBlock::make_if_block(IRInstruction* insn) { IRList::iterator false_block; curr = mc->make_if_block(curr, insn, &false_block); return new MethodBlock(false_block, mc); } IRList::iterator MethodCreator::make_if_block(IRList::iterator curr, IRInstruction* insn, IRList::iterator* false_block) { return meth_code->make_if_block(++curr, insn, false_block); } MethodBlock* MethodBlock::make_if_else_block(IRInstruction* insn, MethodBlock** true_block) { IRList::iterator if_it; IRList::iterator else_it; curr = mc->make_if_else_block(curr, insn, &if_it, &else_it); *true_block = new MethodBlock(else_it, mc); return new MethodBlock(if_it, mc); } IRList::iterator MethodCreator::make_if_else_block( IRList::iterator curr, IRInstruction* insn, IRList::iterator* false_block, IRList::iterator* true_block) { return meth_code->make_if_else_block(++curr, insn, false_block, true_block); } MethodBlock* MethodBlock::make_switch_block( IRInstruction* insn, std::map<SwitchIndices, MethodBlock*>& cases) { IRList::iterator default_it; std::map<SwitchIndices, IRList::iterator> mt_cases; for (const auto& cases_it : cases) { mt_cases[cases_it.first] = curr; } curr = mc->make_switch_block(curr, insn, &default_it, mt_cases); for (auto& cases_it : cases) { cases_it.second = new MethodBlock(mt_cases[cases_it.first], mc); } return new MethodBlock(default_it, mc); } IRList::iterator MethodCreator::make_switch_block( IRList::iterator curr, IRInstruction* insn, IRList::iterator* default_block, std::map<SwitchIndices, IRList::iterator>& cases) { return meth_code->make_switch_block(++curr, insn, default_block, cases); } std::vector<Location> MethodCreator::get_reg_args() { std::vector<Location> args; uint32_t args_size = method->get_proto()->get_args()->size(); if (!is_static(method)) { args_size += 1; } args.insert(args.end(), locals.begin(), locals.begin() + args_size); return args; } MethodCreator::MethodCreator(DexMethod* meth) : method(meth), meth_code(meth->get_code()) { always_assert_log(meth->is_concrete(), "Method must be concrete or use the other ctor"); load_locals(meth); main_block = new MethodBlock(meth_code->main_block(), this); } MethodCreator::MethodCreator(DexMethodRef* ref, DexAccessFlags access, std::unique_ptr<DexAnnotationSet> anno, bool with_debug_item) : MethodCreator(ref->get_class(), ref->get_name(), ref->get_proto(), access, std::move(anno), with_debug_item){}; MethodCreator::MethodCreator(DexType* cls, const DexString* name, DexProto* proto, DexAccessFlags access, std::unique_ptr<DexAnnotationSet> anno, bool with_debug_item) : method(static_cast<DexMethod*>(DexMethod::make_method(cls, name, proto))), m_with_debug_item(with_debug_item) { always_assert_log(!method->is_concrete(), "Method already defined"); if (anno) { method->attach_annotation_set(std::move(anno)); } method->make_concrete( access, !(access & (ACC_STATIC | ACC_PRIVATE | ACC_CONSTRUCTOR))); method->set_deobfuscated_name(show_deobfuscated(method)); method->set_code(std::make_unique<IRCode>(method, 0)); meth_code = method->get_code(); if (with_debug_item) { meth_code->set_debug_item(std::make_unique<DexDebugItem>()); } load_locals(method); main_block = new MethodBlock(meth_code->main_block(), this); } DexMethod* MethodCreator::create() { auto param_insns = meth_code->get_param_instructions(); auto param_reg = meth_code->get_registers_size(); transform::RegMap reg_map; // allocate all the params at the end of the register frame for (const auto& mie : boost::adaptors::reverse(param_insns)) { auto insn = mie.insn; param_reg -= insn->dest_is_wide() ? 2 : 1; reg_map[insn->dest()] = param_reg; if (insn->dest_is_wide()) { reg_map[insn->dest() + 1] = param_reg + 1; } } // now allocate the rest at the start size_t temp_reg{0}; for (size_t i = 0; i < meth_code->get_registers_size(); ++i) { if (reg_map.find(i) != reg_map.end()) { continue; } reg_map[i] = temp_reg++; } always_assert(temp_reg == param_reg); transform::remap_registers(meth_code, reg_map); if (m_with_debug_item) { // Insert a fake position entry for redex generated method when we // add debug item. auto main_block_it = meth_code->get_param_instructions().end(); auto position = std::make_unique<DexPosition>(0); position->bind(DexString::make_string(show(method)), DexString::make_string("RedexGenerated")); meth_code->insert_before(main_block_it, std::move(position)); } return method; } DexMethod* MethodCreator::make_static_from(DexMethod* meth, DexClass* target_cls) { auto name = get_name(meth); return make_static_from(name, meth, target_cls); } DexMethod* MethodCreator::make_static_from(const DexString* name, DexMethod* meth, DexClass* target_cls) { auto proto = make_static_sig(meth); return make_static_from(name, proto, meth, target_cls); } DexMethod* MethodCreator::make_static_from(const DexString* name, DexProto* proto, DexMethod* meth, DexClass* target_cls) { redex_assert(!is_static(meth)); redex_assert(!method::is_init(meth) && !method::is_clinit(meth)); auto smeth = static_cast<DexMethod*>( DexMethod::make_method(target_cls->get_type(), name, proto)); smeth->make_concrete(meth->get_access() | ACC_STATIC, meth->release_code(), false); target_cls->add_method(smeth); return smeth; }