PageRenderTime 58ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 1ms

/chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.cc

https://github.com/chromium/chromium
C++ | 390 lines | 258 code | 65 blank | 67 comment | 33 complexity | 1031d2bbbfb94b78822248ec2e7a39ef MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.h"
  5. #include <stddef.h>
  6. #include <algorithm>
  7. #include <string>
  8. #include <vector>
  9. #include "base/cxx17_backports.h"
  10. #include "base/files/file_path.h"
  11. #include "base/files/memory_mapped_file.h"
  12. #include "base/metrics/histogram_functions.h"
  13. #include "base/scoped_native_library.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "base/win/pe_image.h"
  16. #include "components/safe_browsing/core/common/proto/csd.pb.h"
  17. namespace safe_browsing {
  18. namespace {
  19. // The maximum amount of bytes that can be reported as modified by VerifyModule.
  20. const int kMaxModuleModificationBytes = 256;
  21. struct Export {
  22. Export(void* addr, const std::string& name);
  23. ~Export();
  24. bool operator<(const Export& other) const {
  25. return addr < other.addr;
  26. }
  27. void* addr;
  28. std::string name;
  29. };
  30. Export::Export(void* addr, const std::string& name) : addr(addr), name(name) {
  31. }
  32. Export::~Export() {
  33. }
  34. struct ModuleVerificationState {
  35. explicit ModuleVerificationState(HMODULE hModule);
  36. ModuleVerificationState(const ModuleVerificationState&) = delete;
  37. ModuleVerificationState& operator=(const ModuleVerificationState&) = delete;
  38. ~ModuleVerificationState();
  39. base::win::PEImageAsData disk_peimage;
  40. // The module's preferred base address minus the base address it actually
  41. // loaded at.
  42. intptr_t image_base_delta;
  43. // The location of the disk_peimage module's code section minus that of the
  44. // mem_peimage module's code section.
  45. intptr_t code_section_delta;
  46. // Set true if the relocation table contains a reloc of type that we don't
  47. // currently handle.
  48. bool unknown_reloc_type;
  49. // The start of the code section of the in-memory binary.
  50. uint8_t* mem_code_addr;
  51. // The start of the code section of the on-disk binary.
  52. uint8_t* disk_code_addr;
  53. // The size of the binary's code section.
  54. uint32_t code_size;
  55. // The exports of the DLL, sorted by address in ascending order.
  56. std::vector<Export> exports;
  57. // The location in the in-memory binary of the latest reloc encountered by
  58. // |EnumRelocsCallback|.
  59. uint8_t* last_mem_reloc_position;
  60. // The location in the on-disk binary of the latest reloc encountered by
  61. // |EnumRelocsCallback|.
  62. uint8_t* last_disk_reloc_position;
  63. // The number of bytes with a different value on disk and in memory, as
  64. // computed by |VerifyModule|.
  65. int bytes_different;
  66. // The module state protobuf object that |VerifyModule| will populate.
  67. ClientIncidentReport_EnvironmentData_Process_ModuleState* module_state;
  68. };
  69. ModuleVerificationState::ModuleVerificationState(HMODULE hModule)
  70. : disk_peimage(hModule),
  71. image_base_delta(0),
  72. code_section_delta(0),
  73. unknown_reloc_type(false),
  74. mem_code_addr(nullptr),
  75. disk_code_addr(nullptr),
  76. code_size(0),
  77. last_mem_reloc_position(nullptr),
  78. last_disk_reloc_position(nullptr),
  79. bytes_different(0),
  80. module_state(nullptr) {
  81. }
  82. ModuleVerificationState::~ModuleVerificationState() {
  83. }
  84. // Find which export a modification at address |mem_address| is in. Looks for
  85. // the largest export address still smaller than |mem_address|. |start| and
  86. // |end| must come from a sorted collection.
  87. std::vector<Export>::const_iterator FindModifiedExport(
  88. uint8_t* mem_address,
  89. std::vector<Export>::const_iterator start,
  90. std::vector<Export>::const_iterator end) {
  91. // We get the largest export address still smaller than |addr|. It is
  92. // possible that |addr| belongs to some nonexported function located
  93. // between this export and the following one.
  94. Export addr(reinterpret_cast<void*>(mem_address), std::string());
  95. return std::upper_bound(start, end, addr);
  96. }
  97. // Checks each byte in a subsection of the module's code section against the
  98. // corresponding byte on disk, returning the number of bytes differing between
  99. // the two. |state.exports| must be sorted.
  100. int ExamineByteRangeDiff(uint8_t* disk_start,
  101. uint8_t* mem_start,
  102. ptrdiff_t range_size,
  103. ModuleVerificationState* state) {
  104. int bytes_different = 0;
  105. std::vector<Export>::const_iterator export_it = state->exports.begin();
  106. for (uint8_t* end = mem_start + range_size; mem_start < end;
  107. ++mem_start, ++disk_start) {
  108. if (*disk_start == *mem_start)
  109. continue;
  110. auto* modification = state->module_state->add_modification();
  111. // Store the address at which the modification starts on disk, relative to
  112. // the beginning of the image.
  113. modification->set_file_offset(
  114. disk_start - reinterpret_cast<uint8_t*>(state->disk_peimage.module()));
  115. // Find the export containing this modification.
  116. std::vector<Export>::const_iterator modified_export_it =
  117. FindModifiedExport(mem_start, export_it, state->exports.end());
  118. // No later byte can belong to an earlier export.
  119. export_it = modified_export_it;
  120. if (modified_export_it != state->exports.begin())
  121. modification->set_export_name((modified_export_it - 1)->name);
  122. const uint8_t* range_start = mem_start;
  123. while (mem_start < end && *disk_start != *mem_start) {
  124. ++disk_start;
  125. ++mem_start;
  126. }
  127. int bytes_in_modification = mem_start - range_start;
  128. bytes_different += bytes_in_modification;
  129. modification->set_byte_count(bytes_in_modification);
  130. modification->set_modified_bytes(
  131. range_start,
  132. std::min(bytes_in_modification, kMaxModuleModificationBytes));
  133. }
  134. return bytes_different;
  135. }
  136. bool AddrIsInCodeSection(void* address,
  137. uint8_t* code_addr,
  138. uint32_t code_size) {
  139. return (code_addr <= address && address < code_addr + code_size);
  140. }
  141. bool EnumRelocsCallback(const base::win::PEImage& mem_peimage,
  142. WORD type,
  143. void* address,
  144. void* cookie) {
  145. ModuleVerificationState* state =
  146. reinterpret_cast<ModuleVerificationState*>(cookie);
  147. // If not in the code section return true to continue to the next reloc.
  148. if (!AddrIsInCodeSection(address, state->mem_code_addr, state->code_size))
  149. return true;
  150. switch (type) {
  151. case IMAGE_REL_BASED_ABSOLUTE: // 0
  152. break;
  153. case IMAGE_REL_BASED_HIGHLOW: // 3
  154. {
  155. // The range to inspect is from the last reloc to the current one at
  156. // |ptr|
  157. uint8_t* ptr = reinterpret_cast<uint8_t*>(address);
  158. // If the last relocation was not before this one in the binary,
  159. // there's an issue in the reloc section. We can't really recover from
  160. // that so flag state as such so the error can be logged.
  161. if (ptr < state->last_mem_reloc_position)
  162. return false;
  163. // Check which bytes of the relocation are not accounted for by the
  164. // rebase. If the beginning of the relocation is modified by something
  165. // other than the rebase, extend the verification range to include those
  166. // bytes since they are considered part of a modification.
  167. uint32_t relocated = *reinterpret_cast<uint32_t*>(ptr);
  168. uint32_t original = relocated + state->image_base_delta;
  169. uint8_t* original_reloc_bytes = reinterpret_cast<uint8_t*>(&original);
  170. uint8_t* reloc_disk_position = ptr + state->code_section_delta;
  171. size_t unaccounted_reloc_bytes = 0;
  172. while (unaccounted_reloc_bytes < sizeof(uint32_t) &&
  173. original_reloc_bytes[unaccounted_reloc_bytes] !=
  174. reloc_disk_position[unaccounted_reloc_bytes]) {
  175. ++unaccounted_reloc_bytes;
  176. }
  177. // If the entire reloc was modified, return true to let the next
  178. // EnumReloc track it as part of a larger modification.
  179. if (unaccounted_reloc_bytes == sizeof(uint32_t))
  180. return true;
  181. ptrdiff_t range_size = ptr +
  182. unaccounted_reloc_bytes -
  183. state->last_mem_reloc_position;
  184. state->bytes_different += ExamineByteRangeDiff(
  185. state->last_disk_reloc_position,
  186. state->last_mem_reloc_position,
  187. range_size,
  188. state);
  189. // Starting after the verified range, check if the relocation ends with
  190. // modified bytes. If it does, include them in the following range to be
  191. // verified as they're considered modified. Otherwise, the following
  192. // range will start right after the current reloc.
  193. size_t unmodified_reloc_byte_count = unaccounted_reloc_bytes;
  194. while (unmodified_reloc_byte_count < sizeof(uint32_t) &&
  195. original_reloc_bytes[unmodified_reloc_byte_count] ==
  196. reloc_disk_position[unmodified_reloc_byte_count]) {
  197. ++unmodified_reloc_byte_count;
  198. }
  199. state->last_disk_reloc_position +=
  200. range_size + unmodified_reloc_byte_count;
  201. state->last_mem_reloc_position +=
  202. range_size + unmodified_reloc_byte_count;
  203. }
  204. break;
  205. case IMAGE_REL_BASED_DIR64: // 10
  206. break;
  207. default:
  208. // TODO(robertshield): Find a reliable description of the behaviour of the
  209. // remaining types of relocation and handle them.
  210. state->unknown_reloc_type = true;
  211. break;
  212. }
  213. return true;
  214. }
  215. bool EnumExportsCallback(const base::win::PEImage& mem_peimage,
  216. DWORD ordinal,
  217. DWORD hint,
  218. LPCSTR name,
  219. PVOID function_addr,
  220. LPCSTR forward,
  221. PVOID cookie) {
  222. std::vector<Export>* exports = reinterpret_cast<std::vector<Export>*>(cookie);
  223. if (name)
  224. exports->push_back(Export(function_addr, std::string(name)));
  225. return true;
  226. }
  227. } // namespace
  228. bool GetCodeAddrsAndSize(const base::win::PEImage& mem_peimage,
  229. const base::win::PEImageAsData& disk_peimage,
  230. uint8_t** mem_code_addr,
  231. uint8_t** disk_code_addr,
  232. uint32_t* code_size) {
  233. DWORD base_of_code = mem_peimage.GetNTHeaders()->OptionalHeader.BaseOfCode;
  234. // Get the address and size of the code section in the loaded module image.
  235. PIMAGE_SECTION_HEADER mem_code_header =
  236. mem_peimage.GetImageSectionFromAddr(mem_peimage.RVAToAddr(base_of_code));
  237. if (mem_code_header == NULL)
  238. return false;
  239. *mem_code_addr = reinterpret_cast<uint8_t*>(
  240. mem_peimage.RVAToAddr(mem_code_header->VirtualAddress));
  241. // If the section is padded with zeros when mapped then |VirtualSize| can be
  242. // larger. Alternatively, |SizeOfRawData| can be rounded up to align
  243. // according to OptionalHeader.FileAlignment.
  244. *code_size = std::min(mem_code_header->Misc.VirtualSize,
  245. mem_code_header->SizeOfRawData);
  246. // Get the address of the code section in the module mapped as data from disk.
  247. DWORD disk_code_offset = 0;
  248. if (!mem_peimage.ImageAddrToOnDiskOffset(
  249. reinterpret_cast<void*>(*mem_code_addr), &disk_code_offset))
  250. return false;
  251. *disk_code_addr =
  252. reinterpret_cast<uint8_t*>(disk_peimage.module()) + disk_code_offset;
  253. return true;
  254. }
  255. bool VerifyModule(
  256. const wchar_t* module_name,
  257. ClientIncidentReport_EnvironmentData_Process_ModuleState* module_state,
  258. int* num_bytes_different) {
  259. using ModuleState = ClientIncidentReport_EnvironmentData_Process_ModuleState;
  260. *num_bytes_different = 0;
  261. module_state->set_name(base::WideToUTF8(module_name));
  262. module_state->set_modified_state(ModuleState::MODULE_STATE_UNKNOWN);
  263. // Get module handle, load a copy from disk as data and create PEImages.
  264. HMODULE module_handle = NULL;
  265. if (!GetModuleHandleEx(0, module_name, &module_handle))
  266. return false;
  267. base::ScopedNativeLibrary native_library(module_handle);
  268. WCHAR module_path[MAX_PATH] = {};
  269. DWORD length =
  270. GetModuleFileName(module_handle, module_path, base::size(module_path));
  271. if (!length || length == base::size(module_path))
  272. return false;
  273. base::MemoryMappedFile mapped_module;
  274. if (!mapped_module.Initialize(base::FilePath(module_path)))
  275. return false;
  276. ModuleVerificationState state(
  277. reinterpret_cast<HMODULE>(const_cast<uint8_t*>(mapped_module.data())));
  278. base::win::PEImage mem_peimage(module_handle);
  279. if (!mem_peimage.VerifyMagic() || !state.disk_peimage.VerifyMagic())
  280. return false;
  281. // Get the list of exports and sort them by address for efficient lookups.
  282. mem_peimage.EnumExports(EnumExportsCallback, &state.exports);
  283. std::sort(state.exports.begin(), state.exports.end());
  284. // Get the addresses of the code sections then calculate |code_section_delta|
  285. // and |image_base_delta|.
  286. if (!GetCodeAddrsAndSize(mem_peimage,
  287. state.disk_peimage,
  288. &state.mem_code_addr,
  289. &state.disk_code_addr,
  290. &state.code_size))
  291. return false;
  292. state.module_state = module_state;
  293. state.last_mem_reloc_position = state.mem_code_addr;
  294. state.last_disk_reloc_position = state.disk_code_addr;
  295. state.code_section_delta = state.disk_code_addr - state.mem_code_addr;
  296. uint8_t* preferred_image_base = reinterpret_cast<uint8_t*>(
  297. state.disk_peimage.GetNTHeaders()->OptionalHeader.ImageBase);
  298. state.image_base_delta =
  299. preferred_image_base - reinterpret_cast<uint8_t*>(mem_peimage.module());
  300. state.last_mem_reloc_position = state.mem_code_addr;
  301. state.last_disk_reloc_position = state.disk_code_addr;
  302. // Enumerate relocations and verify the bytes between them.
  303. bool scan_complete = mem_peimage.EnumRelocs(EnumRelocsCallback, &state);
  304. if (scan_complete) {
  305. size_t range_size =
  306. state.code_size - (state.last_mem_reloc_position - state.mem_code_addr);
  307. // Inspect the last chunk spanning from the furthest relocation to the end
  308. // of the code section.
  309. state.bytes_different += ExamineByteRangeDiff(
  310. state.last_disk_reloc_position,
  311. state.last_mem_reloc_position,
  312. range_size,
  313. &state);
  314. }
  315. *num_bytes_different = state.bytes_different;
  316. // Report STATE_MODIFIED if any difference was found, regardless of whether or
  317. // not the entire module was scanned. Report STATE_UNMODIFIED only if the
  318. // entire module was scanned and understood.
  319. if (state.bytes_different)
  320. module_state->set_modified_state(ModuleState::MODULE_STATE_MODIFIED);
  321. else if (!state.unknown_reloc_type && scan_complete)
  322. module_state->set_modified_state(ModuleState::MODULE_STATE_UNMODIFIED);
  323. return scan_complete;
  324. }
  325. } // namespace safe_browsing