@ 261107.1
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 "LIEF/runtime/windows/LdrDataTableEntry.hpp"
#include "LIEF/runtime/windows/PEB.hpp"
#include "LIEF/runtime/windows/Host.hpp"
#include "LIEF/optional.hpp"
#include "LIEF/utils.hpp"

#include "internal_utils.hpp"

#include <spdlog/fmt/fmt.h>

#include <cstddef>
#include <cstdint>
#include <sstream>

namespace LIEF::runtime::windows {
namespace details {
// Primitive-typed taken from winternl.h

// LIST_ENTRY
struct list_entry {
  uintptr_t Flink;
  uintptr_t Blink;
};

// UNICODE_STRING.
struct unicode_string {
  uint16_t Length;
  uint16_t MaximumLength;
  uintptr_t Buffer;
};

// PEB_LDR_DATA
struct peb_ldr_data {
  /// Size of the structure
  uint32_t Length;
  /// If the loader data section is initialized
  uint8_t Initialized;

  /// Handle to the subsystem
  uintptr_t SsHandle;

  /// Doubly linked list (Load order)
  list_entry InLoadOrderModuleList;

  /// Doubly linked list (Memory placement order)
  list_entry InMemoryOrderModuleList;

  /// Doubly linked list (Initialization order)
  list_entry InInitializationOrderModuleList;

  /// Pointer to module currently being loaded
  uintptr_t EntryInProgress;

  /// Indicates if process shutdown has started
  uintptr_t ShutdownInProgress;

  /// Thread ID performing the shutdown
  uintptr_t ShutdownThreadId;
};

// RTL_BALANCED_NODE: node of the red-black trees that the Windows 8+ loader
// uses to index modules by base address and by mapping. Three pointer-sized
// words on every architecture.
struct rtl_balanced_node {
  uintptr_t Left;
  uintptr_t Right;
  uintptr_t ParentValue;
};

// LDR_DATA_TABLE_ENTRY

// clang-format off
struct ldr_data_table_entry {
  list_entry InLoadOrderLinks;           // 0x00
  list_entry InMemoryOrderLinks;         // 0x10
  list_entry InInitializationOrderLinks; // 0x20
  uintptr_t DllBase;                     // 0x30
  uintptr_t EntryPoint;                  // 0x38
  uint32_t SizeOfImage;                  // 0x40
  unicode_string FullDllName;            // 0x48
  unicode_string BaseDllName;            // 0x58

  // struct {
  //     ULONG PackagedBinary : 1;
  //     ULONG MarkedForRemoval : 1;
  //     ULONG ImageDll : 1;
  //     ULONG LoadNotificationsSent : 1;
  //     ULONG TelemetryEntryProcessed : 1;
  //     ULONG ProcessStaticImport : 1;
  //     ULONG InLegacyLists : 1;
  //     ULONG InIndexes : 1;
  //     ULONG ShimDll : 1;
  //     ULONG InExceptionTable : 1;
  //     ULONG LoadInProgress : 1;
  //     ULONG LoadConfigProcessed : 1;
  //     ULONG EntryProcessed : 1;
  //     ULONG ProtectDelayLoad : 1;
  //     ULONG ReservedFlags3 : 2;
  //     ULONG DontCallForThreads : 1;
  //     ULONG ProcessAttachCalled : 1;
  //     ULONG ProcessAttachFailed : 1;
  //     ULONG CorDeferredValidate : 1;
  //     ULONG CorImage : 1;
  //     ULONG DontRelocate : 1;
  //     ULONG CorILOnly : 1;
  //     ULONG ChpeImage : 1;
  //     ULONG ChpeEmulatorImage : 1;
  //     ULONG ReservedFlags5 : 1;
  //     ULONG Redirected : 1;
  //     ULONG ReservedFlags6 : 2;
  //     ULONG CompatDatabaseProcessed : 1;
  // };
  // TODO(romain): we might want to expose this flag as the bitfield
  //               defined above.
  uint32_t Flags;                        // 0x68
  uint16_t ObsoleteLoadCount;            // 0x6c
  uint16_t TlsIndex;                     // 0x6e
  list_entry HashLinks;                  // 0x70
  uint32_t TimeDateStamp;                // 0x80
  uintptr_t EntryPointActivationContext; // 0x88
  uintptr_t Lock;                        // 0x90

  // Modern dependency graph and loading structures introduced in Windows 8.
  uintptr_t DdagNode;                    // 0x98
  list_entry NodeModuleLink;             // 0xa0
  uintptr_t LoadContext;                 // 0xb0
  uintptr_t ParentDllBase;               // 0xb8
  uintptr_t SwitchBackContext;           // 0xc0

  // Red-black trees for fast memory/mapping lookups.
  rtl_balanced_node BaseAddressIndexNode; // 0xc8
  rtl_balanced_node MappingInfoIndexNode; // 0xe0

  uintptr_t OriginalBase;                 // 0xf8
  int64_t LoadTime;                       // 0x100 (LARGE_INTEGER)
  uint32_t BaseNameHashValue;             // 0x108
  int32_t LoadReason;                     // 0x10c (LDR_DLL_LOAD_REASON)
  uint32_t ImplicitPathOptions;           // 0x110
  uint32_t ReferenceCount;                // 0x114
  uint32_t DependentLoadFlags;            // 0x118

  // Security and integrity (Windows 10+).
  uint8_t SigningLevel;                   // 0x11c
  uint32_t CheckSum;                      // 0x120

  // Hot-patching (Windows 11+).
  uintptr_t ActivePatchImageBase;         // 0x128
  uint32_t HotPatchState;                 // 0x130 (LDR_HOT_PATCH_STATE)
};
// clang-format on


inline constexpr bool is_x64 = sizeof(uintptr_t) == 8;
static_assert(offsetof(ldr_data_table_entry, InLoadOrderLinks) == 0);

static_assert(offsetof(peb_ldr_data, InLoadOrderModuleList) ==
                  (is_x64 ? 0x10 : 0x0C),
              "Invalid layout for InLoadOrderModuleList");

static_assert(!is_x64 || offsetof(ldr_data_table_entry, HotPatchState) == 0x130,
              "Invalid 64-bit layout for HotPatchState");

static_assert(is_x64 || offsetof(ldr_data_table_entry, BaseDllName) == 0x2C,
              "Invalid 32-bit layout for BaseDllName");

class ldr_entry {
  public:
  explicit ldr_entry(const ldr_data_table_entry* raw) :
    raw_(raw) {}

  const ldr_data_table_entry& raw() const {
    return *raw_;
  }

  private:
  const ldr_data_table_entry* raw_ = nullptr;
};

class ldr_entry_it {
  public:
  ldr_entry_it() = default;

  explicit ldr_entry_it(uintptr_t node) :
    node_(node) {}

  bool eq(const ldr_entry_it& other) const {
    return node_ == other.node_;
  }

  std::unique_ptr<ldr_entry_it> clone() const {
    return std::make_unique<ldr_entry_it>(*this);
  }

  void increment() {
    node_ = reinterpret_cast<const list_entry*>(node_)->Flink;
  }

  void decrement() {
    node_ = reinterpret_cast<const list_entry*>(node_)->Blink;
  }

  std::unique_ptr<LdrDataTableEntry> get() const {
    auto impl = std::make_unique<ldr_entry>(
        reinterpret_cast<const ldr_data_table_entry*>(node_)
    );
    return std::make_unique<LdrDataTableEntry>(std::move(impl));
  }

  private:
  uintptr_t node_ = 0;
};

}

inline std::string to_u8(const details::unicode_string& str) {
  if (str.Buffer == 0 || str.Length == 0) {
    return "";
  }
  return u16tou8(reinterpret_cast<const char16_t*>(str.Buffer),
                 str.Length / sizeof(char16_t), /*remove_null_char=*/true);
}


inline bool host_at_least(uint32_t major, uint32_t minor, uint32_t build) {
  return Host::version() >= Host::version_t(major, minor, build);
}

// Windows 8 (6.2)
inline bool has_win8_fields() {
  return host_at_least(6, 2, 9200);
}

// Windows 10 (10.0).
inline bool has_win10_fields() {
  return host_at_least(10, 0, 10240);
}

// Windows 11 (build 22000)
inline bool has_win11_fields() {
  return host_at_least(10, 0, 22000);
}

// ----------------------------------------------------------------------------
// LdrDataTableEntry
// ----------------------------------------------------------------------------
LdrDataTableEntry::LdrDataTableEntry(std::unique_ptr<details::ldr_entry> impl) :
  impl_(std::move(impl)) {}

LdrDataTableEntry::LdrDataTableEntry(LdrDataTableEntry&&) noexcept = default;
LdrDataTableEntry&
    LdrDataTableEntry::operator=(LdrDataTableEntry&&) noexcept = default;

LdrDataTableEntry::~LdrDataTableEntry() = default;

uintptr_t LdrDataTableEntry::dll_base() const {
  return impl_->raw().DllBase;
}

uintptr_t LdrDataTableEntry::entry_point() const {
  return impl_->raw().EntryPoint;
}

uint32_t LdrDataTableEntry::size_of_image() const {
  return impl_->raw().SizeOfImage;
}

std::string LdrDataTableEntry::full_dll_name() const {
  return to_u8(impl_->raw().FullDllName);
}

std::string LdrDataTableEntry::base_dll_name() const {
  return to_u8(impl_->raw().BaseDllName);
}

uint32_t LdrDataTableEntry::flags() const {
  return impl_->raw().Flags;
}

uint16_t LdrDataTableEntry::obsolete_load_count() const {
  return impl_->raw().ObsoleteLoadCount;
}

uint16_t LdrDataTableEntry::tls_index() const {
  return impl_->raw().TlsIndex;
}

uint32_t LdrDataTableEntry::time_date_stamp() const {
  return impl_->raw().TimeDateStamp;
}

uintptr_t LdrDataTableEntry::entry_point_activation_context() const {
  return impl_->raw().EntryPointActivationContext;
}

uintptr_t LdrDataTableEntry::lock() const {
  return impl_->raw().Lock;
}

optional<uintptr_t> LdrDataTableEntry::ddag_node() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().DdagNode;
}

optional<uintptr_t> LdrDataTableEntry::load_context() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().LoadContext;
}

optional<uintptr_t> LdrDataTableEntry::parent_dll_base() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().ParentDllBase;
}

optional<uintptr_t> LdrDataTableEntry::switch_back_context() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().SwitchBackContext;
}

optional<uintptr_t> LdrDataTableEntry::original_base() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().OriginalBase;
}

optional<int64_t> LdrDataTableEntry::load_time() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().LoadTime;
}

optional<uint32_t> LdrDataTableEntry::base_name_hash_value() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().BaseNameHashValue;
}

optional<int32_t> LdrDataTableEntry::load_reason() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().LoadReason;
}

optional<uint32_t> LdrDataTableEntry::implicit_path_options() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().ImplicitPathOptions;
}

optional<uint32_t> LdrDataTableEntry::reference_count() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().ReferenceCount;
}

optional<uint32_t> LdrDataTableEntry::dependent_load_flags() const {
  if (!has_win8_fields()) {
    return nullopt();
  }
  return impl_->raw().DependentLoadFlags;
}

optional<uint8_t> LdrDataTableEntry::signing_level() const {
  if (!has_win10_fields()) {
    return nullopt();
  }
  return impl_->raw().SigningLevel;
}

optional<uint32_t> LdrDataTableEntry::check_sum() const {
  if (!has_win10_fields()) {
    return nullopt();
  }
  return impl_->raw().CheckSum;
}

optional<uintptr_t> LdrDataTableEntry::active_patch_image_base() const {
  if (!has_win11_fields()) {
    return nullopt();
  }
  return impl_->raw().ActivePatchImageBase;
}

optional<uint32_t> LdrDataTableEntry::hot_patch_state() const {
  if (!has_win11_fields()) {
    return nullopt();
  }
  return impl_->raw().HotPatchState;
}

std::string LdrDataTableEntry::to_string() const {
  static constexpr auto WIDTH = 40;
  std::ostringstream oss;
  oss << fmt::format("{:{}} {:#018x}\n", "Base address (DllBase):", WIDTH,
                     dll_base())
      << fmt::format("{:{}} {:#018x}\n", "Entry point:", WIDTH, entry_point())
      << fmt::format("{:{}} {:#08x}\n", "Size of image:", WIDTH, size_of_image())
      << fmt::format("{:{}} {}\n", "Full DLL name:", WIDTH, full_dll_name())
      << fmt::format("{:{}} {}\n", "Base DLL name:", WIDTH, base_dll_name())
      << fmt::format("{:{}} {:#010x}\n", "Flags:", WIDTH, flags())
      << fmt::format("{:{}} {}\n", "Obsolete load count:", WIDTH,
                     obsolete_load_count())
      << fmt::format("{:{}} {}\n", "TLS index:", WIDTH, tls_index())
      << fmt::format("{:{}} {:#010x}\n", "Timestamp:", WIDTH, time_date_stamp())
      << fmt::format("{:{}} {:#018x}\n", "Entry point activation context:", WIDTH,
                     entry_point_activation_context())
      << fmt::format("{:{}} {:#018x}\n", "Lock:", WIDTH, lock());

  if (auto val = ddag_node()) {
    oss << fmt::format("{:{}} {:#018x}\n", "DDAG node:", WIDTH, *val);
  }

  if (auto val = load_context()) {
    oss << fmt::format("{:{}} {:#018x}\n", "Load context:", WIDTH, *val);
  }

  if (auto val = parent_dll_base()) {
    oss << fmt::format("{:{}} {:#018x}\n", "Parent DLL base:", WIDTH, *val);
  }

  if (auto val = switch_back_context()) {
    oss << fmt::format("{:{}} {:#018x}\n", "Switch-back context:", WIDTH, *val);
  }

  if (auto val = original_base()) {
    oss << fmt::format("{:{}} {:#018x}\n", "Original base:", WIDTH, *val);
  }

  if (auto val = load_time()) {
    oss << fmt::format("{:{}} {}\n", "Load time:", WIDTH, *val);
  }

  if (auto val = base_name_hash_value()) {
    oss << fmt::format("{:{}} {:#010x}\n", "Base name hash value:", WIDTH, *val);
  }

  if (auto val = load_reason()) {
    oss << fmt::format("{:{}} {}\n", "Load reason:", WIDTH, *val);
  }

  if (auto val = implicit_path_options()) {
    oss << fmt::format("{:{}} {:#010x}\n", "Implicit path options:", WIDTH, *val);
  }

  if (auto val = reference_count()) {
    oss << fmt::format("{:{}} {}\n", "Reference count:", WIDTH, *val);
  }

  if (auto val = dependent_load_flags()) {
    oss << fmt::format("{:{}} {:#010x}\n", "Dependent load flags:", WIDTH, *val);
  }

  if (auto val = signing_level()) {
    oss << fmt::format("{:{}} {}\n", "Signing level:", WIDTH, *val);
  }

  if (auto val = check_sum()) {
    oss << fmt::format("{:{}} {:#010x}\n", "Checksum:", WIDTH, *val);
  }

  if (auto val = active_patch_image_base()) {
    oss << fmt::format("{:{}} {:#018x}\n", "Active patch image base:", WIDTH,
                       *val);
  }

  if (auto val = hot_patch_state()) {
    oss << fmt::format("{:{}} {:#010x}\n", "Hot patch state:", WIDTH, *val);
  }

  return oss.str();
}

// ----------------------------------------------------------------------------
// LdrDataTableEntry::Iterator
// ----------------------------------------------------------------------------
LdrDataTableEntry::Iterator::Iterator() = default;

LdrDataTableEntry::Iterator::Iterator(
    std::unique_ptr<details::ldr_entry_it> impl
) :
  impl_(std::move(impl)) {}

LdrDataTableEntry::Iterator::Iterator(const Iterator& other) :
  impl_(other.impl_ ? other.impl_->clone() : nullptr) {}

LdrDataTableEntry::Iterator&
    LdrDataTableEntry::Iterator::operator=(const Iterator& other) {
  if (this != &other) {
    impl_ = other.impl_ ? other.impl_->clone() : nullptr;
    cached_.reset();
  }
  return *this;
}

LdrDataTableEntry::Iterator::Iterator(Iterator&&) noexcept = default;
LdrDataTableEntry::Iterator&
    LdrDataTableEntry::Iterator::operator=(Iterator&&) noexcept = default;

LdrDataTableEntry::Iterator::~Iterator() = default;

bool operator==(const LdrDataTableEntry::Iterator& LHS,
                const LdrDataTableEntry::Iterator& RHS) {
  return LHS.impl_->eq(*RHS.impl_);
}

LdrDataTableEntry::Iterator& LdrDataTableEntry::Iterator::operator++() {
  impl_->increment();
  cached_.reset();
  return *this;
}

LdrDataTableEntry::Iterator& LdrDataTableEntry::Iterator::operator--() {
  impl_->decrement();
  cached_.reset();
  return *this;
}

void LdrDataTableEntry::Iterator::load() const {
  if (cached_ == nullptr) {
    cached_ = impl_->get();
  }
}

const LdrDataTableEntry& LdrDataTableEntry::Iterator::operator*() const {
  load();
  return *cached_;
}

const LdrDataTableEntry* LdrDataTableEntry::Iterator::operator->() const {
  load();
  return cached_.get();
}

std::unique_ptr<LdrDataTableEntry> LdrDataTableEntry::Iterator::yield() {
  load();
  return std::move(cached_);
}

PEB::entries_it PEB::entries() const {
  const uintptr_t ldr_addr = ldr();
  if (ldr_addr == 0) {
    return make_empty_iterator<LdrDataTableEntry>();
  }

  const auto* ldr = reinterpret_cast<const details::peb_ldr_data*>(ldr_addr);

  // The list head lives inside PEB_LDR_DATA and acts as the past-the-end
  // sentinel of the circular list.
  const uintptr_t head =
      ldr_addr + offsetof(details::peb_ldr_data, InLoadOrderModuleList);

  auto begin = LdrDataTableEntry::Iterator(
      std::make_unique<details::ldr_entry_it>(ldr->InLoadOrderModuleList.Flink)
  );
  auto end =
      LdrDataTableEntry::Iterator(std::make_unique<details::ldr_entry_it>(head));

  return make_range(std::move(begin), std::move(end));
}
}