svc123_null_payload_guard.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. #include <array>
  2. #include <atomic>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <cstdio>
  6. #include <cstring>
  7. #include <string_view>
  8. #include "../../../core/logging/log.h"
  9. #include "../../hooking/detour.h"
  10. #include "../../patterns/image_scan.h"
  11. #include "internal.h"
  12. namespace sunrise::client::hooks::queuez {
  13. namespace {
  14. using patterns::scan_main_image_unique;
  15. using patterns::signature;
  16. using patterns::signature_length;
  17. /**
  18. * The update-notification handler, reached only through an indirect method dispatch, so it has no
  19. * call site to anchor on. Matched on its own prologue, stack-cookie store and first message read.
  20. */
  21. constexpr std::string_view kHandlerSignatureText =
  22. "40 53 B8 ? ? ? ? E8 ? ? ? ? 48 2B E0 48 8B 05 ? ? ? ? 48 33 C4 48 89 84 24 ? ? ? ? 41 "
  23. "8B 08";
  24. /** Compiled pattern bytes of the signature text above. */
  25. constexpr auto kHandlerSignature =
  26. signature<signature_length(kHandlerSignatureText)>(kHandlerSignatureText);
  27. /** Fields of the dispatched message record, as byte offsets from its base. */
  28. struct MessageLayout {
  29. /** Message identity, read by the handler before the payload. */
  30. static constexpr std::size_t id = 0;
  31. /** Payload pointer. The handler reads through it with no null check. */
  32. static constexpr std::size_t payload = 8;
  33. };
  34. /** Verdict returned for a skipped message. It is the handler's own no-op result. */
  35. constexpr char kNotHandled = 0;
  36. using Handler = char(__fastcall*)(void*, void*, std::byte*);
  37. hooking::detour::Handle g_handle{};
  38. std::atomic<Handler> g_original{nullptr};
  39. std::atomic_bool g_reported{false};
  40. /**
  41. * Drops an update notification whose payload is null. When the source object holds no entries, the
  42. * emit leaves the descriptor pair zeroed and the handler reads through it anyway. That fault
  43. * unwinds out of the dispatching tick, so the message is skipped here.
  44. * @param self Borrowed handler instance.
  45. * @param context Borrowed dispatch context.
  46. * @param message Borrowed message record, or null.
  47. * @return The handler's own result, or the not-handled verdict for a skipped message.
  48. */
  49. __declspec(noinline) char __fastcall handler(void* self,
  50. void* context,
  51. std::byte* message) noexcept {
  52. std::uintptr_t payload = 0;
  53. if (message != nullptr) {
  54. std::memcpy(&payload, message + MessageLayout::payload, sizeof payload);
  55. }
  56. if (payload == 0) {
  57. if (!g_reported.exchange(true, std::memory_order_relaxed)) {
  58. core::log::write(core::log::Channel::client,
  59. core::log::Level::warn,
  60. "ev=queuez stage=notification result=skipped reason=null_payload");
  61. }
  62. return kNotHandled;
  63. }
  64. const Handler original = g_original.load(std::memory_order_acquire);
  65. const char result = original != nullptr ? original(self, context, message) : kNotHandled;
  66. std::uint32_t messageId = 0;
  67. if (message != nullptr) {
  68. std::memcpy(&messageId, message + MessageLayout::id, sizeof messageId);
  69. }
  70. std::array<char, core::log::kLineCapacity> line{};
  71. const int count = std::snprintf(line.data(),
  72. line.size(),
  73. "ev=queuez stage=native_handler result=%d message_id=%u "
  74. "payload=0x%llX",
  75. static_cast<int>(result),
  76. messageId,
  77. static_cast<unsigned long long>(payload));
  78. if (count > 0) {
  79. core::log::write(core::log::Channel::client,
  80. core::log::Level::debug,
  81. {line.data(), static_cast<std::size_t>(count)});
  82. }
  83. return result;
  84. }
  85. } // namespace
  86. /**
  87. * Attaches the update-notification null-payload guard.
  88. * @return True when the target is found and the detour attaches.
  89. */
  90. bool install_null_payload_guard() noexcept {
  91. if (g_handle.attached) {
  92. return true;
  93. }
  94. std::byte* const target = scan_main_image_unique(kHandlerSignature, "queuez_update_handler");
  95. if (target == nullptr) {
  96. core::log::write(core::log::Channel::client,
  97. core::log::Level::warn,
  98. "ev=queuez stage=notification result=fail reason=target");
  99. return false;
  100. }
  101. const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&handler)};
  102. if (!hooking::detour::install(spec, g_handle)) {
  103. core::log::write(core::log::Channel::client,
  104. core::log::Level::warn,
  105. "ev=queuez stage=notification result=fail reason=attach");
  106. return false;
  107. }
  108. g_original.store(reinterpret_cast<Handler>(g_handle.original), std::memory_order_release);
  109. core::log::write(core::log::Channel::client,
  110. core::log::Level::info,
  111. "ev=queuez stage=notification result=ok");
  112. return true;
  113. }
  114. /** Detaches the null-payload guard. */
  115. void uninstall_null_payload_guard() noexcept {
  116. if (g_handle.attached) {
  117. (void)hooking::detour::uninstall(g_handle);
  118. }
  119. g_original.store(nullptr, std::memory_order_release);
  120. g_reported.store(false, std::memory_order_release);
  121. }
  122. /** @return True while the null-payload guard is attached. */
  123. bool null_payload_guard_installed() noexcept {
  124. return g_handle.attached;
  125. }
  126. } // namespace sunrise::client::hooks::queuez