registry.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. #include "registry.h"
  2. #include <Windows.h>
  3. #include <array>
  4. #include <cstdint>
  5. #include <cstring>
  6. namespace sunrise::client::patterns {
  7. namespace {
  8. /** Returned by next_candidate when a range holds no further anchor byte. */
  9. constexpr std::size_t kNoCandidate = static_cast<std::size_t>(-1);
  10. /** One count per distinct byte value. */
  11. constexpr std::size_t kByteValueCount = 256;
  12. /** Most ranges one fingerprint describes. No PE image carries more sections than this. */
  13. constexpr std::size_t kFingerprintCapacity = 96;
  14. /** How often each byte value occurs across one set of scanned ranges. */
  15. struct ByteCounts {
  16. std::array<std::uint64_t, kByteValueCount> values{};
  17. };
  18. /** Identity of the range set one histogram was built from. */
  19. struct Fingerprint {
  20. std::array<const std::byte*, kFingerprintCapacity> data{};
  21. std::array<std::size_t, kFingerprintCapacity> size{};
  22. std::size_t count{};
  23. /** False for a range set too large to describe, which must never match a stored print. */
  24. bool valid{};
  25. };
  26. /**
  27. * The byte histogram and the ranges it came from.
  28. * Building it costs one traversal of the image. Without this cache every pattern would pay that
  29. * traversal, which is the very cost the anchor choice exists to avoid.
  30. */
  31. struct FrequencyCache {
  32. SRWLOCK lock{SRWLOCK_INIT};
  33. Fingerprint fingerprint{};
  34. ByteCounts counts{};
  35. };
  36. FrequencyCache g_frequency;
  37. /** @return Fingerprint of one range set, invalid when it holds more ranges than one can describe. */
  38. [[nodiscard]] Fingerprint fingerprint_of(std::span<const ImageRange> image) noexcept {
  39. Fingerprint print{};
  40. if (image.size() > kFingerprintCapacity) {
  41. return print;
  42. }
  43. for (std::size_t index = 0; index < image.size(); ++index) {
  44. print.data[index] = image[index].bytes.data();
  45. print.size[index] = image[index].bytes.size();
  46. }
  47. print.count = image.size();
  48. print.valid = true;
  49. return print;
  50. }
  51. /** @return True when both fingerprints name the same ranges in the same order. */
  52. [[nodiscard]] bool same_ranges(const Fingerprint& left, const Fingerprint& right) noexcept {
  53. if (!left.valid || !right.valid || left.count != right.count) {
  54. return false;
  55. }
  56. for (std::size_t index = 0; index < left.count; ++index) {
  57. if (left.data[index] != right.data[index] || left.size[index] != right.size[index]) {
  58. return false;
  59. }
  60. }
  61. return true;
  62. }
  63. /** Counts every byte value across one range set. */
  64. void count_bytes(std::span<const ImageRange> image, ByteCounts& counts) noexcept {
  65. counts = {};
  66. for (const ImageRange range : image) {
  67. for (const std::byte value : range.bytes) {
  68. ++counts.values[std::to_integer<unsigned char>(value)];
  69. }
  70. }
  71. }
  72. /**
  73. * Reads the byte histogram for one range set, building it on the first request.
  74. * @param image Ranges about to be scanned.
  75. * @param counts Receives a copy, so no caller holds the cache lock while it scans.
  76. */
  77. void byte_counts(std::span<const ImageRange> image, ByteCounts& counts) noexcept {
  78. const Fingerprint wanted = fingerprint_of(image);
  79. AcquireSRWLockExclusive(&g_frequency.lock);
  80. if (!same_ranges(g_frequency.fingerprint, wanted)) {
  81. count_bytes(image, g_frequency.counts);
  82. g_frequency.fingerprint = wanted;
  83. }
  84. counts = g_frequency.counts;
  85. ReleaseSRWLockExclusive(&g_frequency.lock);
  86. }
  87. /**
  88. * The one exact byte a pattern's candidate search keys on.
  89. * A pattern with no exact byte cannot be scanned, so the anchor doubles as the validity check.
  90. */
  91. struct Anchor {
  92. /** Position of the anchor byte inside the pattern. */
  93. std::size_t index{};
  94. /** The byte itself, held unsigned so it reaches memchr without sign extension. */
  95. unsigned char value{};
  96. /** False for a pattern the sweep cannot scan, which is the pattern it must reject. */
  97. bool valid{};
  98. };
  99. /**
  100. * Picks the anchor byte for one pattern.
  101. * The candidate search keys on this byte, so the rarest exact byte is the one that lets memchr
  102. * skip the most. Taking the first exact byte instead lands on a REX prefix for most function
  103. * prologues, and those are among the most common bytes there are in compiled x64: the sweep then
  104. * stops to verify millions of times per pattern.
  105. * @param pattern Pattern name, bytes, and exact-byte mask.
  106. * @param counts How often each byte value occurs in the ranges about to be scanned.
  107. * @return A valid anchor when the pattern has a name, bytes, and at least one exact byte.
  108. */
  109. [[nodiscard]] Anchor anchor_of(const Pattern& pattern, const ByteCounts& counts) noexcept {
  110. if (pattern.name.empty() || pattern.bytes.empty()) {
  111. return {};
  112. }
  113. Anchor best{};
  114. std::uint64_t bestCount = 0;
  115. for (std::size_t index = 0; index < pattern.bytes.size(); ++index) {
  116. if (!pattern.bytes[index].exact) {
  117. continue;
  118. }
  119. const auto value = std::to_integer<unsigned char>(pattern.bytes[index].value);
  120. const std::uint64_t occurrences = counts.values[value];
  121. // The earliest byte wins a tie, so one image always picks the same anchor.
  122. if (best.valid && occurrences >= bestCount) {
  123. continue;
  124. }
  125. best = Anchor{index, value, true};
  126. bestCount = occurrences;
  127. }
  128. return best;
  129. }
  130. /**
  131. * Tests one pattern at one bounded image offset.
  132. * @param image Executable image bytes.
  133. * @param pattern Masked pattern bytes.
  134. * @return True when every exact byte matches.
  135. */
  136. [[nodiscard]] bool matches_at(std::span<const std::byte> image,
  137. std::size_t offset,
  138. std::span<const PatternByte> pattern) noexcept {
  139. // next_candidate never returns an offset past the last whole match, so this cannot wrap.
  140. if (pattern.size() > image.size() - offset) {
  141. return false;
  142. }
  143. for (std::size_t index = 0; index < pattern.size(); ++index) {
  144. if (pattern[index].exact && image[offset + index] != pattern[index].value) {
  145. return false;
  146. }
  147. }
  148. return true;
  149. }
  150. /**
  151. * Finds the next offset at or after one start where the anchor byte lines up.
  152. * The bytes in between cannot begin a match, so memchr skips them at memory speed instead of the
  153. * sweep testing every one of them.
  154. * @param range One executable range.
  155. * @param patternSize Pattern length, which bounds the last offset that can hold a whole match.
  156. * @param anchor Valid anchor for that pattern.
  157. * @param from First offset to consider.
  158. * @return Candidate offset, or kNoCandidate when the range holds no further one.
  159. */
  160. [[nodiscard]] std::size_t next_candidate(std::span<const std::byte> range,
  161. std::size_t patternSize,
  162. const Anchor& anchor,
  163. std::size_t from) noexcept {
  164. if (patternSize > range.size()) {
  165. return kNoCandidate;
  166. }
  167. const std::size_t lastOffset = range.size() - patternSize;
  168. if (from > lastOffset) {
  169. return kNoCandidate;
  170. }
  171. // The anchor sits at offset + index, so the search window is the offset window shifted by it.
  172. const std::size_t first = from + anchor.index;
  173. const std::size_t last = lastOffset + anchor.index;
  174. const void* const hit = std::memchr(range.data() + first, anchor.value, last - first + 1);
  175. if (hit == nullptr) {
  176. return kNoCandidate;
  177. }
  178. const auto* const found = static_cast<const std::byte*>(hit);
  179. return static_cast<std::size_t>(found - range.data()) - anchor.index;
  180. }
  181. } // namespace
  182. /** Resolves every registered pattern against one executable range. */
  183. bool resolve_all(std::span<std::byte> image,
  184. std::span<const Pattern> patterns,
  185. std::span<Match> matches) noexcept {
  186. const ImageRange range{image};
  187. return resolve_all(std::span(&range, 1), patterns, matches);
  188. }
  189. /** Resolves every pattern across disjoint executable image ranges. */
  190. bool resolve_all(std::span<const ImageRange> image,
  191. std::span<const Pattern> patterns,
  192. std::span<Match> matches) noexcept {
  193. if (patterns.size() != matches.size()) {
  194. return false;
  195. }
  196. ByteCounts counts;
  197. byte_counts(image, counts);
  198. for (std::size_t index = 0; index < patterns.size(); ++index) {
  199. const Anchor anchor = anchor_of(patterns[index], counts);
  200. matches[index] = anchor.valid ? Match{MatchStatus::missing, nullptr} : Match{};
  201. if (!anchor.valid) {
  202. continue;
  203. }
  204. Match& match = matches[index];
  205. const std::span<const PatternByte> bytes = patterns[index].bytes;
  206. for (const ImageRange range : image) {
  207. std::size_t offset = 0;
  208. while (match.status != MatchStatus::ambiguous) {
  209. offset = next_candidate(range.bytes, bytes.size(), anchor, offset);
  210. if (offset == kNoCandidate) {
  211. break;
  212. }
  213. if (matches_at(range.bytes, offset, bytes)) {
  214. // A second match invalidates the address instead of choosing one.
  215. match = match.status == MatchStatus::missing
  216. ? Match{MatchStatus::unique, range.bytes.data() + offset}
  217. : Match{MatchStatus::ambiguous, nullptr};
  218. }
  219. ++offset;
  220. }
  221. // Ambiguous is final, so the remaining ranges cannot change this pattern's result.
  222. if (match.status == MatchStatus::ambiguous) {
  223. break;
  224. }
  225. }
  226. }
  227. return true;
  228. }
  229. /** Collects bounded matches for one signature that is expected to repeat. */
  230. std::size_t collect_matches(std::span<const ImageRange> image,
  231. const Pattern& pattern,
  232. std::span<std::byte*> output) noexcept {
  233. ByteCounts counts;
  234. byte_counts(image, counts);
  235. const Anchor anchor = anchor_of(pattern, counts);
  236. if (!anchor.valid || output.empty()) {
  237. return 0;
  238. }
  239. std::size_t count = 0;
  240. for (const ImageRange range : image) {
  241. for (std::size_t offset = next_candidate(range.bytes, pattern.bytes.size(), anchor, 0);
  242. offset != kNoCandidate;
  243. offset = next_candidate(range.bytes, pattern.bytes.size(), anchor, offset + 1)) {
  244. if (!matches_at(range.bytes, offset, pattern.bytes)) {
  245. continue;
  246. }
  247. output[count++] = range.bytes.data() + offset;
  248. if (count == output.size()) {
  249. return count;
  250. }
  251. }
  252. }
  253. return count;
  254. }
  255. } // namespace sunrise::client::patterns