current commit
260929.11 · deps: update lief to 1.0.0
/* Copyright 2017 - 2026 R. Thomas
 * Copyright 2017 - 2026 Quarkslab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <fstream>
#include <climits>
#include "LIEF/BinaryStream/SpanStream.hpp"
#include "logging.hpp"
#include "LIEF/DEX/File.hpp"
#include "LIEF/DEX/utils.hpp"
#include "LIEF/DEX/instructions.hpp"
#include "LIEF/DEX/Class.hpp"
#include "LIEF/DEX/Method.hpp"
#include "LIEF/DEX/Prototype.hpp"
#include "LIEF/DEX/hash.hpp"
#include "LIEF/DEX/Type.hpp"
#include "LIEF/DEX/Field.hpp"
#include "DEX/Structures.hpp"

#if defined(LIEF_JSON_SUPPORT)
  #include "visitors/json.hpp"
#endif


namespace LIEF::DEX {

File::File() :
  name_{"classes.dex"} {}
File::~File() = default;

dex_version_t File::version() const {
  Header::magic_t m = header().magic();
  span<const uint8_t> data(m.data(), m.size());
  SpanStream stream(data);
  return DEX::version(stream);
}

std::string File::save(const std::string& path, bool deoptimize) const {
  if (path.empty()) {
    if (!name().empty()) {
      return save(name());
    }
    return save("classes.dex");
  }

  if (std::ofstream ifs{path, std::ios::binary | std::ios::trunc}) {
    if (deoptimize) {
      const std::vector<uint8_t> raw = this->raw(deoptimize);
      ifs.write(reinterpret_cast<const char*>(raw.data()), raw.size());
    } else {
      ifs.write(reinterpret_cast<const char*>(original_data_.data()),
                original_data_.size());
    }
    return path;
  }

  return "";
}


std::vector<uint8_t> File::raw(bool deoptimize) const {
  if (!deoptimize) {
    return original_data_;
  }
  dex2dex_info_t dex2dex_info = this->dex2dex_info();

  if (dex2dex_info.empty()) {
    return original_data_;
  }

  std::vector<uint8_t> raw = original_data_;

  for (const std::unique_ptr<Method>& method : methods_) {
    if (method->bytecode().empty()) {
      continue;
    }
    const uint32_t code_item_offset = method->code_offset();
    const uint8_t* inst_start = raw.data() + code_item_offset;
    uint8_t* inst_ptr = raw.data() + code_item_offset;
    uint8_t* inst_end = inst_ptr + method->bytecode().size();
    dex2dex_method_info_t meth_info = method->dex2dex_info();

    while (inst_ptr < inst_end) {
      uint16_t dex_pc = (inst_ptr - inst_start) / sizeof(uint16_t);
      auto opcode = static_cast<OPCODES>(*inst_ptr);
      uint32_t value = UINT_MAX;

      if (meth_info.find(dex_pc) != meth_info.end()) {
        value = meth_info[dex_pc];
      }

      // Skip packed-switch, sparse-switch, fill-array instructions
      if (is_switch_array(inst_ptr, inst_end)) {
        const size_t payload_size = switch_array_size(inst_ptr, inst_end);
        if (!valid_inst_size(inst_ptr, inst_end, payload_size)) {
          break;
        }
        inst_ptr += payload_size;
        continue;
      }

      const size_t inst_size = inst_size_from_opcode(opcode);
      if (!valid_inst_size(inst_ptr, inst_end, inst_size)) {
        break;
      }

      switch (opcode) {
        case OPCODES::OP_NOP:
        {
          // deoptimize_nop(inst_ptr, 0);
          break;
        }

        case OPCODES::OP_RETURN_VOID_NO_BARRIER:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] return-void-no-barrier -> return-void", dex_pc);
          deoptimize_return(inst_ptr, 0);
          break;
        }

        case OPCODES::OP_IGET_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iget-quick -> iget@{:#x}", dex_pc, value);
          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iget-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IGET);
          break;
        }

        case OPCODES::OP_IGET_WIDE_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iget-wide-quick -> iget-wide@{:d}", dex_pc, value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iget-wide-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IGET_WIDE);
          break;
        }

        case OPCODES::OP_IGET_OBJECT_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iget-object-quick -> iget-object@{:d}", dex_pc,
                     value);
          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN(
                "Unresolved instruction {}.{} at {:#06x} (iget-object-quick)",
                method->cls()->fullname(), method->name(), dex_pc
            );
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value,
                                           OPCODES::OP_IGET_OBJECT);
          break;
        }

        case OPCODES::OP_IPUT_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iput-quick -> iput@{:d}", dex_pc, value);
          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iput-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IPUT);
          break;
        }

        case OPCODES::OP_IPUT_WIDE_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iput-wide-quick -> iput-wide@{:d}", dex_pc, value);
          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iput-wide-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IPUT_WIDE);
          break;
        }

        case OPCODES::OP_IPUT_OBJECT_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iput-object-quick -> iput-objecte@{:d}", dex_pc,
                     value);
          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN(
                "Unresolved instruction {}.{} at {:#06x} (iput-object-quick)",
                method->cls()->fullname(), method->name(), dex_pc
            );
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value,
                                           OPCODES::OP_IPUT_OBJECT);
          break;
        }

        case OPCODES::OP_INVOKE_VIRTUAL_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] invoke-virtual-quick -> invoke-virtual@{:d}",
                     dex_pc, value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN(
                "Unresolved instruction {}.{} at {:#06x} (invoke-virtual-quick)",
                method->cls()->fullname(), method->name(), dex_pc
            );
            break;
          }
          deoptimize_invoke_virtual(inst_ptr, value, OPCODES::OP_INVOKE_VIRTUAL);
          break;
        }

        case OPCODES::OP_INVOKE_VIRTUAL_RANGE_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE(
              "[{:06x}] invoke-virtual-quick/range -> invoke-virtual/range @{:d}",
              dex_pc, value
          );

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} "
                      "(invoke-virtual-quick/range)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_invoke_virtual(inst_ptr, value,
                                    OPCODES::OP_INVOKE_VIRTUAL_RANGE);
          break;
        }

        case OPCODES::OP_IPUT_BOOLEAN_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iput-boolean-quick -> iput-boolean@{:d}", dex_pc,
                     value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN(
                "Unresolved instruction {}.{} at {:#06x} (iput-boolean-quick)",
                method->cls()->fullname(), method->name(), dex_pc
            );
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value,
                                           OPCODES::OP_IPUT_BOOLEAN);
          break;
        }

        case OPCODES::OP_IPUT_BYTE_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iput-byte-quick -> iput-byte @{:d}", dex_pc, value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iput-byte-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IPUT_BYTE);
          break;
        }

        case OPCODES::OP_IPUT_CHAR_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iput-char-quick -> iput-char @{:d}", dex_pc, value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iput-char-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IPUT_CHAR);
          break;
        }

        case OPCODES::OP_IPUT_SHORT_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iput-short-quick -> iput-short @{:d}", dex_pc,
                     value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iput-short)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value,
                                           OPCODES::OP_IPUT_SHORT);
          break;
        }

        case OPCODES::OP_IGET_BOOLEAN_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iget-boolean-quick -> iget-boolean @{:d}", dex_pc,
                     value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN(
                "Unresolved instruction {}.{} at {:#06x} (iget-boolean-quick)",
                method->cls()->fullname(), method->name(), dex_pc
            );
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value,
                                           OPCODES::OP_IGET_BOOLEAN);
          break;
        }

        case OPCODES::OP_IGET_BYTE_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iget-byte-quick -> iget-byte @{:d}", dex_pc, value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iget-byte-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IGET_BYTE);
          break;
        }

        case OPCODES::OP_IGET_CHAR_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iget-char-quick -> iget-char @{:d}", dex_pc, value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iget-char-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value, OPCODES::OP_IGET_CHAR);
          break;
        }

        case OPCODES::OP_IGET_SHORT_QUICK:
        {
          LIEF_TRACE("{}.{}", method->cls()->fullname(), method->name());
          LIEF_TRACE("[{:06x}] iget-short-quick -> iget-short @{:d}", dex_pc,
                     value);

          if (static_cast<int32_t>(value) == -1) {
            LIEF_WARN("Unresolved instruction {}.{} at {:#06x} (iget-short-quick)",
                      method->cls()->fullname(), method->name(), dex_pc);
            break;
          }
          deoptimize_instance_field_access(inst_ptr, value,
                                           OPCODES::OP_IGET_SHORT);
          break;
        }
        default:
        {
        }
      }
      inst_ptr += inst_size;
    }
  }

  return raw;
}

void File::deoptimize_nop(uint8_t* inst_ptr, uint32_t /*value*/) {
  *inst_ptr = OPCODES::OP_CHECK_CAST;
}

void File::deoptimize_return(uint8_t* inst_ptr, uint32_t /*value*/) {
  *inst_ptr = OPCODES::OP_RETURN_VOID;
}

void File::deoptimize_invoke_virtual(uint8_t* inst_ptr, uint32_t value,
                                     OPCODES new_inst) {
  *inst_ptr = new_inst;
  reinterpret_cast<uint16_t*>(inst_ptr)[1] = value;
}

void File::deoptimize_instance_field_access(uint8_t* inst_ptr, uint32_t value,
                                            OPCODES new_inst) {
  *inst_ptr = new_inst;
  reinterpret_cast<uint16_t*>(inst_ptr)[1] = value;
}

const std::string& File::name() const {
  return name_;
}


const std::string& File::location() const {
  return location_;
}

const Header& File::header() const {
  return header_;
}

Header& File::header() {
  return const_cast<Header&>(static_cast<const File*>(this)->header());
}

File::it_const_classes File::classes() const {
  return class_list_;
}

File::it_classes File::classes() {
  return class_list_;
}

bool File::has_class(const std::string& class_name) const {
  return classes_.find(Class::fullname_normalized(class_name)) != classes_.end();
}

const Class* File::get_class(const std::string& class_name) const {
  auto it_cls = classes_.find(Class::fullname_normalized(class_name));
  if (it_cls == classes_.end()) {
    return nullptr;
  }
  return it_cls->second;
}

Class* File::get_class(const std::string& class_name) {
  return const_cast<Class*>(static_cast<const File*>(this)->get_class(class_name));
}


const Class* File::get_class(size_t index) const {
  if (index >= classes_.size()) {
    return nullptr;
  }
  return class_list_[index].get();
}

Class* File::get_class(size_t index) {
  return const_cast<Class*>(static_cast<const File*>(this)->get_class(index));
}


dex2dex_info_t File::dex2dex_info() const {
  dex2dex_info_t info;
  for (const auto& p : classes_) {
    dex2dex_class_info_t class_info = p.second->dex2dex_info();
    if (!class_info.empty()) {
      info.emplace(p.second, std::move(class_info));
    }
  }
  return info;
}

std::string File::dex2dex_json_info() const {

#if defined(LIEF_JSON_SUPPORT)
  json mapping = json::object();

  // Iterate over the quickened classes
  for (const auto& class_map : dex2dex_info()) {
    const Class* clazz = class_map.first;
    const std::string& class_name = clazz->fullname();
    mapping[class_name] = json::object();

    const dex2dex_class_info_t& class_info = class_map.second;
    // Iterate over the methods within the class
    for (const auto& method_map : class_info) {

      // Index of the method within the DEX file
      uint32_t index = method_map.first->index();

      mapping[class_name][std::to_string(index)] = json::object();

      for (const auto& pc_index : method_map.second) {
        mapping[class_name][std::to_string(index)]
               [std::to_string(pc_index.first)] = pc_index.second;
      }
    }
  }
  return mapping.dump();
#else
  return "";
#endif
}

File::it_const_methods File::methods() const {
  return methods_;
}

File::it_methods File::methods() {
  return methods_;
}

File::it_const_fields File::fields() const {
  return fields_;
}

File::it_fields File::fields() {
  return fields_;
}

File::it_const_strings File::strings() const {
  return strings_;
}

File::it_strings File::strings() {
  return strings_;
}

File::it_const_types File::types() const {
  return types_;
}

File::it_types File::types() {
  return types_;
}

File::it_const_prototypes File::prototypes() const {
  return prototypes_;
}

File::it_prototypes File::prototypes() {
  return prototypes_;
}

const MapList& File::map() const {
  return map_;
}

MapList& File::map() {
  return map_;
}

void File::name(const std::string& name) {
  name_ = name;
}

void File::location(const std::string& location) {
  location_ = location;
}

void File::add_class(std::unique_ptr<Class> cls) {
  classes_.emplace(cls->fullname(), cls.get());
  class_list_.push_back(std::move(cls));
}

void File::accept(Visitor& visitor) const {
  visitor.visit(*this);
}


std::ostream& operator<<(std::ostream& os, const File& file) {
  os << "DEX File " << file.name() << " Version: " << std::dec << file.version();
  if (!file.location().empty()) {
    os << " - " << file.location();
  }
  os << '\n';

  os << "Header" << '\n';
  os << "======" << '\n';

  os << file.header();

  os << '\n';

  os << "Map" << '\n';
  os << "===" << '\n';

  os << file.map();

  os << '\n';
  return os;
}


}