@ 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 <iterator>

#ifndef MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS
  #define MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS
#endif

#include "logging.hpp"
#include "psa/crypto_builtin_primitives.h"

#include "LIEF/PE/utils.hpp"
#include "LIEF/PE/Binary.hpp"
#include "LIEF/PE/Import.hpp"
#include "LIEF/PE/Section.hpp"
#include "LIEF/PE/ImportEntry.hpp"
#include "LIEF/BinaryStream/SpanStream.hpp"
#include "LIEF/BinaryStream/FileStream.hpp"
#include "PE/Structures.hpp"

#include "LIEF/utils.hpp"

#include "utils/ordinals_lookup_tables/libraries_table.hpp"
#include "utils/ordinals_lookup_tables_std/libraries_table.hpp"

#include "hash_stream.hpp"
#include "internal_utils.hpp"


namespace LIEF::PE {

static constexpr size_t SIZEOF_OPT_HEADER_32 = 0xE0;
static constexpr size_t SIZEOF_OPT_HEADER_64 = 0xF0;

inline std::string to_lower(std::string str) {
  std::string lower = str;
  std::transform(str.begin(), str.end(), lower.begin(), ::tolower);
  return lower;
}

bool is_pe(BinaryStream& stream) {
  using signature_t = std::array<char, sizeof(details::PE_Magic)>;
  ScopedStream scoped(stream, 0);
  if (auto dos_header = scoped->read<details::pe_dos_header>()) {
    if (dos_header->Magic != /* MZ */ 0x5a4d) {
      return false;
    }
    scoped->setpos(dos_header->AddressOfNewExeHeader);
    if (auto res_sig = scoped->read<signature_t>()) {
      const auto signature = *res_sig;
      return std::equal(std::begin(signature), std::end(signature),
                        std::begin(details::PE_Magic));
    }
  }
  return false;
}

bool is_pe(const std::string& file) {
  if (auto stream = FileStream::from_file(file)) {
    return is_pe(*stream);
  }
  return false;
}


bool is_pe(const std::vector<uint8_t>& raw) {
  if (auto stream = SpanStream::from_vector(raw)) {
    return is_pe(*stream);
  }
  return false;
}


result<PE_TYPE> get_type_from_stream(BinaryStream& stream) {
  const uint64_t cpos = stream.pos();
  stream.setpos(0);
  auto dos_hdr = stream.read<details::pe_dos_header>();
  if (!dos_hdr) {
    LIEF_ERR("Failed to read the DOS header structure");
    return make_error_code(dos_hdr.error());
  }
  stream.setpos(dos_hdr->AddressOfNewExeHeader);
  if (!stream.can_read<details::pe_header>()) {
    LIEF_ERR("Failed to read the PE header");
    return make_error_code(lief_errors::read_error);
  }

  auto header = stream.read<details::pe_header>();
  if (!header) {
    LIEF_ERR("Failed to read the PE header");
    return make_error_code(header.error());
  }

  const size_t sizeof_opt_header = header->SizeOfOptionalHeader;
  if (sizeof_opt_header != SIZEOF_OPT_HEADER_32 &&
      sizeof_opt_header != SIZEOF_OPT_HEADER_64)
  {
    LIEF_WARN(
        "SizeOfOptionalHeader seems invalid: {:#x}. Expecting {:#x} or {:#x}",
        sizeof_opt_header, SIZEOF_OPT_HEADER_32, SIZEOF_OPT_HEADER_64
    );
  }

  auto opt_hdr = stream.read<details::pe32_optional_header>();
  if (!opt_hdr) {
    LIEF_ERR("Failed to read the PE optional header");
    return make_error_code(opt_hdr.error());
  }
  const auto type = static_cast<PE_TYPE>(opt_hdr->Magic);
  stream.setpos(cpos); // Restore the original position

  /*
   * We first try to determine PE's type from the OptionalHeader's Magic.
   * In some cases (cf. https://github.com/lief-project/LIEF/issues/644),
   * these magic bytes can be "corrupted" without afecting the executing.
   * So the second test to determine the PE's type is to check the value
   * of SizeOfOptionalHeader we should match sizeof(pe32_optional_header)
   * for a 32 bits PE or sizeof(pe64_optional_header) for a 64 bits PE file
   */
  if (type == PE_TYPE::PE32 || type == PE_TYPE::PE32_PLUS) {
    return type;
  }

  if (sizeof_opt_header == SIZEOF_OPT_HEADER_32) {
    return PE_TYPE::PE32;
  }

  if (sizeof_opt_header == SIZEOF_OPT_HEADER_64) {
    return PE_TYPE::PE32_PLUS;
  }

  LIEF_ERR("Failed to determine PE type (PE32 / PE32+)");
  return make_error_code(lief_errors::file_format_error);
}

result<PE_TYPE> get_type(const std::string& file) {
  if (auto stream = FileStream::from_file(file)) {
    return get_type_from_stream(*stream);
  }
  return make_error_code(lief_errors::file_format_error);
}

result<PE_TYPE> get_type(const std::vector<uint8_t>& raw) {
  if (auto stream = SpanStream::from_vector(raw)) {
    return get_type_from_stream(*stream);
  }
  return make_error_code(lief_errors::file_format_error);
}


std::string get_imphash_std(const Binary& binary) {
  static const std::set<std::string> ALLOWED_EXT = {"dll", "ocx", "sys"};
  std::vector<uint8_t> md5_buffer(16);
  if (!binary.has_imports()) {
    return "";
  }
  std::string lstr;
  bool first_entry = true;
  hashstream hs(hashstream::HASH::MD5);
  for (const Import& imp : binary.imports()) {
    Import resolved = imp;
    if (auto resolution =
            resolve_ordinals(imp, /* strict */ false, /* use_std */ true))
    {
      resolved = std::move(*resolution);
    }

    size_t ext_idx = resolved.name().find_last_of('.');
    std::string name = resolved.name();
    std::string ext;
    if (ext_idx != std::string::npos) {
      ext = to_lower(resolved.name().substr(ext_idx + 1));
    }
    if (ALLOWED_EXT.find(ext) != ALLOWED_EXT.end()) {
      name = name.substr(0, ext_idx);
    }

    std::string entries_string;
    for (const ImportEntry& e : resolved.entries()) {
      std::string funcname;
      if (e.is_ordinal()) {
        funcname = "ord" + std::to_string(e.ordinal());
      } else {
        funcname = e.name();
      }

      if (!entries_string.empty()) {
        entries_string += ',';
      }
      entries_string.append(name).append(".").append(funcname);
    }
    if (!first_entry) {
      lstr += ',';
    } else {
      first_entry = false;
    }
    lstr += to_lower(entries_string);

    // Use write(uint8_t*, size_t) instead of write(const std::string&) to avoid
    // including the null terminator
    hs.write(reinterpret_cast<const uint8_t*>(lstr.data()), lstr.size());
    lstr.clear();
  }

  return hex_dump(hs.raw(), "");
}


std::string get_imphash_lief(const Binary& binary) {
  std::vector<uint8_t> md5_buffer(16);
  if (!binary.has_imports()) {
    return std::to_string(0);
  }

  Binary::it_const_imports imports = binary.imports();

  std::string import_list;
  for (const Import& imp : imports) {
    Import resolved = imp;
    if (auto resolution = resolve_ordinals(imp)) {
      resolved = std::move(*resolution);
    }

    size_t ext_idx = resolved.name().find_last_of('.');
    std::string name_without_ext = resolved.name();

    if (ext_idx != std::string::npos) {
      name_without_ext = name_without_ext.substr(0, ext_idx);
    }

    std::string entries_string;
    for (const ImportEntry& e : resolved.entries()) {
      if (e.is_ordinal()) {
        entries_string.append(name_without_ext)
            .append(".#")
            .append(std::to_string(e.ordinal()));
      } else {
        entries_string.append(name_without_ext).append(".").append(e.name());
      }
    }
    import_list += to_lower(entries_string);
  }

  std::sort(import_list.begin(), import_list.end(), std::less<>());

  mbedtls_md5(reinterpret_cast<const uint8_t*>(import_list.data()),
              import_list.size(), md5_buffer.data());
  return hex_dump(md5_buffer, "");
}

std::string get_imphash(const Binary& binary, IMPHASH_MODE mode) {
  switch (mode) {
    case IMPHASH_MODE::LIEF:
    {
      return get_imphash_lief(binary);
    }
    case IMPHASH_MODE::PEFILE:
    {
      return get_imphash_std(binary);
    }
  }
  return "";
}

result<Import> resolve_ordinals(const Import& import, bool strict, bool use_std) {
  static const std::unordered_map<std::string, const char* (*)(uint32_t)>
      ordinals_library_tables = {
          {"kernel32.dll", &kernel32_dll_lookup},
          {"ntdll.dll", &ntdll_dll_lookup},
          {"advapi32.dll", &advapi32_dll_lookup},
          {"msvcp110.dll", &msvcp110_dll_lookup},
          {"msvcp120.dll", &msvcp120_dll_lookup},
          {"msvcr100.dll", &msvcr100_dll_lookup},
          {"msvcr110.dll", &msvcr110_dll_lookup},
          {"msvcr120.dll", &msvcr120_dll_lookup},
          {"user32.dll", &user32_dll_lookup},
          {"comctl32.dll", &comctl32_dll_lookup},
          {"ws2_32.dll", &ws2_32_dll_lookup},
          {"shcore.dll", &shcore_dll_lookup},
          {"oleaut32.dll", &oleaut32_dll_lookup},
          {"mfc42u.dll", &mfc42u_dll_lookup},
          {"shlwapi.dll", &shlwapi_dll_lookup},
          {"gdi32.dll", &gdi32_dll_lookup},
          {"shell32.dll", &shell32_dll_lookup},
  };

  static const std::unordered_map<std::string, const char* (*)(uint32_t)>
      imphashstd_ordinals_library_tables = {
          {"ws2_32.dll", &ws2_32_dll_lookup},
          {"wsock32.dll", &ws2_32_dll_lookup},
          {"oleaut32.dll", &oleaut32_dll_lookup},
  };

  using ordinal_resolver_t = const char* (*)(uint32_t);

  Import::it_const_entries entries = import.entries();

  if (std::all_of(entries.begin(), entries.end(),
                  [](const ImportEntry& entry) { return !entry.is_ordinal(); }))
  {
    // LIEF_DEBUG("All imports use name. No ordinal!");
    return import;
  }

  std::string name = to_lower(import.name());

  ordinal_resolver_t ordinal_resolver = nullptr;
  if (use_std) {
    auto it = imphashstd_ordinals_library_tables.find(name);
    if (it != imphashstd_ordinals_library_tables.end()) {
      ordinal_resolver = it->second;
    }
  } else {
    auto it = ordinals_library_tables.find(name);
    if (it != ordinals_library_tables.end()) {
      ordinal_resolver = it->second;
    }
  }

  if (ordinal_resolver == nullptr) {
    if (strict) {
      return make_error_code(lief_errors::not_implemented);
    }
    LIEF_DEBUG("Ordinal lookup table for '{}' not implemented", name);
    return import;
  }

  Import resolved_import = import;
  for (ImportEntry& entry : resolved_import.entries()) {
    if (entry.is_ordinal()) {
      LIEF_DEBUG("Dealing with: {}", to_string(entry));
      const char* entry_name =
          ordinal_resolver(static_cast<uint32_t>(entry.ordinal()));
      if (entry_name == nullptr) {
        if (strict) {
          return make_error_code(lief_errors::not_supported);
        }
        LIEF_DEBUG("Failed to resolve ordinal: #{:d}", entry.ordinal());
        continue;
      }
      entry.data(0);
      entry.name(entry_name);
    }
  }

  return resolved_import;
}
ALGORITHMS algo_from_oid(const std::string& oid) {
  static const std::unordered_map<std::string, ALGORITHMS> OID_MAP = {
      {"2.16.840.1.101.3.4.2.3", ALGORITHMS::SHA_512},
      {"2.16.840.1.101.3.4.2.2", ALGORITHMS::SHA_384},
      {"2.16.840.1.101.3.4.2.1", ALGORITHMS::SHA_256},
      {"1.3.14.3.2.26", ALGORITHMS::SHA_1},

      {"1.2.840.113549.2.5", ALGORITHMS::MD5},
      {"1.2.840.113549.2.4", ALGORITHMS::MD4},
      {"1.2.840.113549.2.2", ALGORITHMS::MD2},

      {"1.2.840.113549.1.1.1", ALGORITHMS::RSA},
      {"1.2.840.10045.2.1", ALGORITHMS::EC},

      {"1.2.840.113549.1.1.4", ALGORITHMS::MD5_RSA},
      {"1.2.840.10040.4.3", ALGORITHMS::SHA1_DSA},
      {"1.2.840.113549.1.1.5", ALGORITHMS::SHA1_RSA},
      {"1.2.840.113549.1.1.11", ALGORITHMS::SHA_256_RSA},
      {"1.2.840.113549.1.1.12", ALGORITHMS::SHA_384_RSA},
      {"1.2.840.113549.1.1.13", ALGORITHMS::SHA_512_RSA},
      {"1.2.840.10045.4.1", ALGORITHMS::SHA1_ECDSA},
      {"1.2.840.10045.4.3.2", ALGORITHMS::SHA_256_ECDSA},
      {"1.2.840.10045.4.3.3", ALGORITHMS::SHA_384_ECDSA},
      {"1.2.840.10045.4.3.4", ALGORITHMS::SHA_512_ECDSA},
  };


  const auto it = OID_MAP.find(oid.c_str());
  if (it == OID_MAP.end()) {
    return ALGORITHMS::UNKNOWN;
  }
  return it->second;
}

}