registry.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #include "registry.h"
  2. #include <cstring>
  3. namespace sunrise::client::patterns {
  4. namespace {
  5. /** Returned by next_candidate when a range holds no further anchor byte. */
  6. constexpr std::size_t kNoCandidate = static_cast<std::size_t>(-1);
  7. /**
  8. * The one exact byte a pattern's candidate search keys on.
  9. * A pattern with no exact byte cannot be scanned, so the anchor doubles as the validity check.
  10. */
  11. struct Anchor {
  12. /** Position of the anchor byte inside the pattern. */
  13. std::size_t index{};
  14. /** The byte itself, held unsigned so it reaches memchr without sign extension. */
  15. unsigned char value{};
  16. /** False for a pattern the sweep cannot scan, which is the pattern it must reject. */
  17. bool valid{};
  18. };
  19. /**
  20. * Picks the anchor byte for one pattern.
  21. * @param pattern Pattern name, bytes, and exact-byte mask.
  22. * @return A valid anchor when the pattern has a name, bytes, and at least one exact byte.
  23. */
  24. [[nodiscard]] Anchor anchor_of(const Pattern& pattern) noexcept {
  25. if (pattern.name.empty() || pattern.bytes.empty()) {
  26. return {};
  27. }
  28. for (std::size_t index = 0; index < pattern.bytes.size(); ++index) {
  29. if (pattern.bytes[index].exact) {
  30. return Anchor{index, std::to_integer<unsigned char>(pattern.bytes[index].value), true};
  31. }
  32. }
  33. return {};
  34. }
  35. /**
  36. * Tests one pattern at one bounded image offset.
  37. * @param image Executable image bytes.
  38. * @param pattern Masked pattern bytes.
  39. * @return True when every exact byte matches.
  40. */
  41. [[nodiscard]] bool matches_at(std::span<const std::byte> image,
  42. std::size_t offset,
  43. std::span<const PatternByte> pattern) noexcept {
  44. // next_candidate never returns an offset past the last whole match, so this cannot wrap.
  45. if (pattern.size() > image.size() - offset) {
  46. return false;
  47. }
  48. for (std::size_t index = 0; index < pattern.size(); ++index) {
  49. if (pattern[index].exact && image[offset + index] != pattern[index].value) {
  50. return false;
  51. }
  52. }
  53. return true;
  54. }
  55. /**
  56. * Finds the next offset at or after one start where the anchor byte lines up.
  57. * The bytes in between cannot begin a match, so memchr skips them at memory speed instead of the
  58. * sweep testing every one of them.
  59. * @param range One executable range.
  60. * @param patternSize Pattern length, which bounds the last offset that can hold a whole match.
  61. * @param anchor Valid anchor for that pattern.
  62. * @param from First offset to consider.
  63. * @return Candidate offset, or kNoCandidate when the range holds no further one.
  64. */
  65. [[nodiscard]] std::size_t next_candidate(std::span<const std::byte> range,
  66. std::size_t patternSize,
  67. const Anchor& anchor,
  68. std::size_t from) noexcept {
  69. if (patternSize > range.size()) {
  70. return kNoCandidate;
  71. }
  72. const std::size_t lastOffset = range.size() - patternSize;
  73. if (from > lastOffset) {
  74. return kNoCandidate;
  75. }
  76. // The anchor sits at offset + index, so the search window is the offset window shifted by it.
  77. const std::size_t first = from + anchor.index;
  78. const std::size_t last = lastOffset + anchor.index;
  79. const void* const hit = std::memchr(range.data() + first, anchor.value, last - first + 1);
  80. if (hit == nullptr) {
  81. return kNoCandidate;
  82. }
  83. const auto* const found = static_cast<const std::byte*>(hit);
  84. return static_cast<std::size_t>(found - range.data()) - anchor.index;
  85. }
  86. } // namespace
  87. /** Resolves every registered pattern against one executable range. */
  88. bool resolve_all(std::span<std::byte> image,
  89. std::span<const Pattern> patterns,
  90. std::span<Match> matches) noexcept {
  91. const ImageRange range{image};
  92. return resolve_all(std::span(&range, 1), patterns, matches);
  93. }
  94. /** Resolves every pattern across disjoint executable image ranges. */
  95. bool resolve_all(std::span<const ImageRange> image,
  96. std::span<const Pattern> patterns,
  97. std::span<Match> matches) noexcept {
  98. if (patterns.size() != matches.size()) {
  99. return false;
  100. }
  101. for (std::size_t index = 0; index < patterns.size(); ++index) {
  102. const Anchor anchor = anchor_of(patterns[index]);
  103. matches[index] = anchor.valid ? Match{MatchStatus::missing, nullptr} : Match{};
  104. if (!anchor.valid) {
  105. continue;
  106. }
  107. Match& match = matches[index];
  108. const std::span<const PatternByte> bytes = patterns[index].bytes;
  109. for (const ImageRange range : image) {
  110. std::size_t offset = 0;
  111. while (match.status != MatchStatus::ambiguous) {
  112. offset = next_candidate(range.bytes, bytes.size(), anchor, offset);
  113. if (offset == kNoCandidate) {
  114. break;
  115. }
  116. if (matches_at(range.bytes, offset, bytes)) {
  117. // A second match invalidates the address instead of choosing one.
  118. match = match.status == MatchStatus::missing
  119. ? Match{MatchStatus::unique, range.bytes.data() + offset}
  120. : Match{MatchStatus::ambiguous, nullptr};
  121. }
  122. ++offset;
  123. }
  124. // Ambiguous is final, so the remaining ranges cannot change this pattern's result.
  125. if (match.status == MatchStatus::ambiguous) {
  126. break;
  127. }
  128. }
  129. }
  130. return true;
  131. }
  132. /** Collects bounded matches for one signature that is expected to repeat. */
  133. std::size_t collect_matches(std::span<const ImageRange> image,
  134. const Pattern& pattern,
  135. std::span<std::byte*> output) noexcept {
  136. const Anchor anchor = anchor_of(pattern);
  137. if (!anchor.valid || output.empty()) {
  138. return 0;
  139. }
  140. std::size_t count = 0;
  141. for (const ImageRange range : image) {
  142. for (std::size_t offset = next_candidate(range.bytes, pattern.bytes.size(), anchor, 0);
  143. offset != kNoCandidate;
  144. offset = next_candidate(range.bytes, pattern.bytes.size(), anchor, offset + 1)) {
  145. if (!matches_at(range.bytes, offset, pattern.bytes)) {
  146. continue;
  147. }
  148. output[count++] = range.bytes.data() + offset;
  149. if (count == output.size()) {
  150. return count;
  151. }
  152. }
  153. }
  154. return count;
  155. }
  156. } // namespace sunrise::client::patterns