@ 261107.7
current
/* 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 <algorithm>
#include <fstream>
#include <iterator>
#include <utility>

#include "logging.hpp"

#include "LIEF/Abstract/Header.hpp"

#include "LIEF/MachO/Builder.hpp"
#include "LIEF/MachO/FatBinary.hpp"
#include "LIEF/MachO/UUIDCommand.hpp"
#include "LIEF/MachO/ExportInfo.hpp"

#include "Object.tcc"

#include "MachO/Builder.tcc"
#include "MachO/Binary.tcc"

namespace LIEF::MachO {

Builder::~Builder() = default;

Builder::Builder(Binary& binary, config_t config) :
  binary_{&binary},
  config_{config} {
  raw_.reserve(std::min<uint64_t>(binary_->original_size(), 1_GB));
  binaries_.push_back(binary_);
}

Builder::Builder(std::vector<Binary*> binaries, config_t config) :
  binaries_{std::move(binaries)},
  config_{config} {}

ok_error_t Builder::build() {
  return binary_->is64_ ? build<details::MachO64>() : build<details::MachO32>();
}

bool Builder::should_swap() const {
  if (binary_ == nullptr) {
    return false;
  }
  switch (binary_->get_abstract_header().endianness()) {
#ifdef __BYTE_ORDER__
  #if defined(__ORDER_LITTLE_ENDIAN__) &&                                         \
      (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
    case LIEF::Header::ENDIANNESS::BIG:
  #elif defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
    case LIEF::Header::ENDIANNESS::LITTLE:
  #endif
      return true;
#else
    // If there is no __BYTE_ORDER__ (e.g. MSVC), assume little-endian
    case LIEF::Header::ENDIANNESS::BIG: return true;
#endif
    default:
      // we're good (or don't know what to do), consider bytes are in the
      // expected order
      return false;
  }
}

template<typename T>
ok_error_t Builder::build() {
  if (binaries_.size() > 1) {
    LIEF_ERR("More than one binary");
    return make_error_code(lief_errors::build_error);
  }

  raw_.set_endian_swap(should_swap());
  linkedit_.set_endian_swap(should_swap());

  // Check if we need to extend some commands
  {
    int32_t original_size = 0;
    int32_t required_size = 0;
    for (std::unique_ptr<LoadCommand>& cmd : binary_->commands_) {
      original_size += cmd->original_data_.size();
      required_size += std::max(cmd->original_data_.size(), get_cmd_size<T>(*cmd));
    }

    int32_t delta = required_size - original_size;

    LIEF_DEBUG("Original commands size:   {:#010x}", original_size);
    LIEF_DEBUG("Required commands size:   {:#010x}", required_size);
    LIEF_DEBUG("Delta:                    {:#010x}", delta);
    LIEF_DEBUG("available_command_space:  {:#010x}",
               binary_->available_command_space_);
    if (delta > 0) {
      ok_error_t is_ok = binary_->ensure_command_space(delta);
      if (!is_ok) {
        return make_error_code(lief_errors::build_error);
      }
      uint64_t cmd_offset = sizeof(typename T::header);
      for (std::unique_ptr<LoadCommand>& cmd : binary_->commands_) {
        const size_t cmd_size =
            std::max(cmd->original_data_.size(), get_cmd_size<T>(*cmd));
        cmd->command_offset_ = cmd_offset;
        cmd_offset += cmd_size;
        if (cmd->original_data_.size() < cmd_size) {
          LIEF_DEBUG("Resizing: {} (+{} bytes)", to_string(cmd->command()),
                     cmd_size - cmd->original_data_.size());
          cmd->original_data_.resize(cmd_size);
          cmd->size_ = cmd_size;
        }
      }
      binary_->header().sizeof_cmds(required_size);
    }
  }

  build_uuid();

  if (config_.linkedit) {
    build_linkedit<T>();
  }

  for (std::unique_ptr<LoadCommand>& cmd : binary_->commands_) {
    if (DylibCommand::classof(cmd.get())) {
      build<T>(*cmd->as<DylibCommand>());
      continue;
    }

    if (DylinkerCommand::classof(cmd.get())) {
      build<T>(*cmd->as<DylinkerCommand>());
      continue;
    }

    if (VersionMin::classof(cmd.get())) {
      build<T>(*cmd->as<VersionMin>());
      continue;
    }

    if (SourceVersion::classof(cmd.get())) {
      build<T>(*cmd->as<SourceVersion>());
      continue;
    }

    if (Routine::classof(cmd.get())) {
      build<T>(*cmd->as<Routine>());
      continue;
    }

    if (MainCommand::classof(cmd.get())) {
      build<T>(*cmd->as<MainCommand>());
      continue;
    }

    if (SubClient::classof(cmd.get())) {
      build<T>(*cmd->as<SubClient>());
      continue;
    }

    if (SubFramework::classof(cmd.get())) {
      build<T>(*cmd->as<SubFramework>());
      continue;
    }

    if (DyldEnvironment::classof(cmd.get())) {
      build<T>(*cmd->as<DyldEnvironment>());
      continue;
    }

    if (ThreadCommand::classof(cmd.get())) {
      build<T>(*cmd->as<ThreadCommand>());
      continue;
    }

    if (BuildVersion::classof(cmd.get())) {
      build<T>(*cmd->as<BuildVersion>());
      continue;
    }

    if (RPathCommand::classof(cmd.get())) {
      build<T>(*cmd->as<RPathCommand>());
      continue;
    }

    if (EncryptionInfo::classof(cmd.get())) {
      build<T>(*cmd->as<EncryptionInfo>());
      continue;
    }

    if (NoteCommand::classof(cmd.get())) {
      build<T>(*cmd->as<NoteCommand>());
      continue;
    }
  }

  build_segments<T>();
  build_load_commands();

  build_header<T>();
  return ok();
}


ok_error_t Builder::build_fat() {

  // If there is only one binary don't build a FAT
  if (binaries_.size() == 1) {
    raw_.write(build_raw(*binaries_.back(), config_));
    return ok();
  }

  build_fat_header();
  constexpr auto fat_header_sz = sizeof(details::fat_header);
  constexpr auto fat_arch_sz = sizeof(details::fat_arch);
  for (size_t i = 0; i < binaries_.size(); ++i) {
    auto* arch =
        reinterpret_cast<details::fat_arch*>(raw_.raw().data() + fat_header_sz +
                                             i * fat_arch_sz);
    std::vector<uint8_t> raw = build_raw(*binaries_[i], config_);

    auto alignment = get_swapped_endian<uint32_t>(arch->align);
    uint32_t offset = align(raw_.size(), 1llu << alignment);

    arch->offset = get_swapped_endian<uint32_t>(offset);
    arch->size = get_swapped_endian<uint32_t>(raw.size());
    raw_.seekp(offset);
    raw_.write(raw);
  }
  return ok();
}

ok_error_t Builder::build_fat_header() {
  LIEF_DEBUG("[+] Building Fat Header");
  static constexpr uint32_t ALIGNMENT = 14; // 4096 / 0x1000
  details::fat_header header{};

  header.magic = static_cast<uint32_t>(MACHO_TYPES::CIGAM_FAT);
  header.nfat_arch = get_swapped_endian<uint32_t>(binaries_.size());

  raw_.seekp(0);
  raw_.write(reinterpret_cast<const uint8_t*>(&header),
             sizeof(details::fat_header));

  for (Binary* binary : binaries_) {
    const Header& header = binary->header();
    details::fat_arch arch_header{};

    arch_header.cputype = get_swapped_endian((uint32_t)header.cpu_type());
    arch_header.cpusubtype = get_swapped_endian((uint32_t)header.cpu_subtype());
    arch_header.offset = 0;
    arch_header.size = 0;
    arch_header.align = get_swapped_endian<uint32_t>(ALIGNMENT);
    raw_.write(reinterpret_cast<const uint8_t*>(&arch_header),
               sizeof(details::fat_arch));
  }
  return ok();
}

ok_error_t Builder::build_load_commands() {
  const auto& binary = binaries_.back();
  // Check if the number of segments is correct
  if (binary->header().nb_cmds() != binary->commands_.size()) {
    LIEF_WARN("Header nb_cmds mismatch: {:d} vs #{:d}", binary->header().nb_cmds(),
              binary->commands_.size());
    return make_error_code(lief_errors::build_error);
  }

  for (const SegmentCommand* segment : binary->segments_) {
    if (LinkEdit::segmentof(*segment) && config_.linkedit) {
      raw_.seekp(linkedit_offset_);
      raw_.write(linkedit_);
      continue;
    }
    span<const uint8_t> segment_content = segment->content();
    raw_.seekp(segment->file_offset());
    raw_.write(segment_content.data(), segment_content.size());
  }

  uint64_t start_offset = binary_->is64_ ? sizeof(details::mach_header_64) :
                                           sizeof(details::mach_header);
  raw_.seekp(start_offset);
  for (size_t i = 0; i < binary_->commands_.size(); ++i) {
    const std::unique_ptr<LoadCommand>& command = binary_->commands_[i];
    span<const uint8_t> data = command->data();

    LIEF_DEBUG("Writing command #{:02d} {:30} offset={:#010x} size={:#010x}", i,
               to_string(command->command()), (uint64_t)raw_.tellp(), data.size());

    raw_.write(data);
  }
  return ok();
}

ok_error_t Builder::build_uuid() {
  auto* uuid_cmd = binary_->command<UUIDCommand>();
  if (uuid_cmd == nullptr) {
    LIEF_DEBUG("[-] No uuid");
    return ok();
  }

  details::uuid_command raw_cmd{};

  raw_cmd.cmd = static_cast<uint32_t>(uuid_cmd->command());
  raw_cmd.cmdsize =
      static_cast<uint32_t>(uuid_cmd->size()); // sizeof(uuid_command)

  const uuid_t& uuid = uuid_cmd->uuid();
  std::copy(uuid.begin(), uuid.end(), raw_cmd.uuid);

  if (uuid_cmd->size() < sizeof(details::uuid_command)) {
    LIEF_WARN("Original data size mismatch for '{}', skipping",
              to_string(uuid_cmd->command()));
    return make_error_code(lief_errors::build_error);
  }

  swap_endian_if_needed(raw_cmd);

  std::copy(reinterpret_cast<const uint8_t*>(&raw_cmd),
            reinterpret_cast<const uint8_t*>(&raw_cmd) +
                sizeof(details::uuid_command),
            uuid_cmd->original_data_.data());
  return ok();
}

const std::vector<uint8_t>& Builder::get_build() {
  return raw_.raw();
}

ok_error_t Builder::write(Binary& binary, const std::string& filename) {
  config_t config;
  return write(binary, filename, config);
}

ok_error_t Builder::write(Binary& binary, const std::string& filename,
                          config_t config) {
  Builder builder{binary, config};
  builder.build();
  builder.write(filename);
  return ok();
}

ok_error_t Builder::write(Binary& binary, std::ostream& out) {
  config_t config;
  return write(binary, out, config);
}

ok_error_t Builder::write(Binary& binary, std::ostream& out, config_t config) {
  Builder builder{binary, config};
  builder.build();
  builder.write(out);
  return ok();
}

ok_error_t Builder::write(Binary& binary, std::vector<uint8_t>& out) {
  config_t config;
  return write(binary, out, config);
}

ok_error_t Builder::write(Binary& binary, std::vector<uint8_t>& out,
                          config_t config) {
  out = build_raw(binary, config);
  return ok();
}

ok_error_t Builder::write(FatBinary& fat, const std::string& filename) {
  config_t config;
  return write(fat, filename, config);
}

ok_error_t Builder::write(FatBinary& fat, const std::string& filename,
                          config_t config) {
  std::vector<Binary*> binaries;
  binaries.reserve(fat.binaries_.size());
  std::transform(fat.binaries_.begin(), fat.binaries_.end(),
                 std::back_inserter(binaries),
                 [](const std::unique_ptr<Binary>& bin) { return bin.get(); });

  Builder builder{std::move(binaries), config};
  builder.build_fat();
  builder.write(filename);
  return ok();
}

ok_error_t Builder::write(FatBinary& fat, std::vector<uint8_t>& out) {
  config_t config;
  return write(fat, out, config);
}

ok_error_t Builder::write(FatBinary& fat, std::vector<uint8_t>& out,
                          config_t config) {
  std::vector<Binary*> binaries;
  binaries.reserve(fat.binaries_.size());
  std::transform(fat.binaries_.begin(), fat.binaries_.end(),
                 std::back_inserter(binaries),
                 [](const std::unique_ptr<Binary>& bin) { return bin.get(); });

  Builder builder{std::move(binaries), config};
  builder.build_fat();
  out = builder.get_build();
  return ok();
}

ok_error_t Builder::write(FatBinary& fat, std::ostream& out) {
  config_t config;
  return write(fat, out, config);
}

ok_error_t Builder::write(FatBinary& fat, std::ostream& out, config_t config) {
  std::vector<Binary*> binaries;
  binaries.reserve(fat.binaries_.size());
  std::transform(fat.binaries_.begin(), fat.binaries_.end(),
                 std::back_inserter(binaries),
                 [](const std::unique_ptr<Binary>& bin) { return bin.get(); });

  Builder builder{std::move(binaries), config};
  builder.build_fat();
  builder.write(out);
  return ok();
}

std::vector<uint8_t> Builder::build_raw(Binary& binary, config_t config) {
  Builder builder{binary, config};
  builder.build();
  return builder.get_build();
}

ok_error_t Builder::write(const std::string& filename) const {
  std::ofstream output_file{filename,
                            std::ios::out | std::ios::binary | std::ios::trunc};
  if (!output_file) {
    LIEF_ERR("Failed to write Mach-O object to '{}'", filename);
    return make_error_code(lief_errors::build_error);
  }
  return write(output_file);
}

ok_error_t Builder::write(std::ostream& os) const {
  std::vector<uint8_t> content;
  raw_.move(content);
  os.write(reinterpret_cast<const char*>(content.data()), content.size());
  return ok();
}

}