1 // Copyright 2017 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
5 #include "chrome/browser/win/conflicts/module_info_util.h"
6
7 #include <windows.h>
8
9 #include <tlhelp32.h>
10 #include <wintrust.h>
11
12 #include <limits>
13 #include <memory>
14 #include <string>
15
16 #include "base/environment.h"
17 #include "base/files/file.h"
18 #include "base/i18n/case_conversion.h"
19 #include "base/logging.h"
20 #include "base/scoped_generic.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/win/pe_image_reader.h"
24 #include "base/win/scoped_handle.h"
25 #include "base/win/wincrypt_shim.h"
26
27 // This must be after wincrypt and wintrust.
28 #include <mscat.h>
29
30 namespace {
31
32 // Helper for scoped tracking an HCERTSTORE.
33 struct ScopedHCERTSTORETraits {
InvalidValue__anon45171b180111::ScopedHCERTSTORETraits34 static HCERTSTORE InvalidValue() { return nullptr; }
Free__anon45171b180111::ScopedHCERTSTORETraits35 static void Free(HCERTSTORE store) { ::CertCloseStore(store, 0); }
36 };
37 using ScopedHCERTSTORE =
38 base::ScopedGeneric<HCERTSTORE, ScopedHCERTSTORETraits>;
39
40 // Helper for scoped tracking an HCRYPTMSG.
41 struct ScopedHCRYPTMSGTraits {
InvalidValue__anon45171b180111::ScopedHCRYPTMSGTraits42 static HCRYPTMSG InvalidValue() { return nullptr; }
Free__anon45171b180111::ScopedHCRYPTMSGTraits43 static void Free(HCRYPTMSG message) { ::CryptMsgClose(message); }
44 };
45 using ScopedHCRYPTMSG = base::ScopedGeneric<HCRYPTMSG, ScopedHCRYPTMSGTraits>;
46
47 // Returns the "Subject" field from the digital signature in the provided
48 // binary, if any is present. Returns an empty string on failure.
GetSubjectNameInFile(const base::FilePath & filename)49 base::string16 GetSubjectNameInFile(const base::FilePath& filename) {
50 ScopedHCERTSTORE store;
51 ScopedHCRYPTMSG message;
52
53 // Find the crypto message for this filename.
54 {
55 HCERTSTORE temp_store = nullptr;
56 HCRYPTMSG temp_message = nullptr;
57 bool result =
58 !!CryptQueryObject(CERT_QUERY_OBJECT_FILE, filename.value().c_str(),
59 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
60 CERT_QUERY_FORMAT_FLAG_BINARY, 0, nullptr, nullptr,
61 nullptr, &temp_store, &temp_message, nullptr);
62 store.reset(temp_store);
63 message.reset(temp_message);
64 if (!result)
65 return base::string16();
66 }
67
68 // Determine the size of the signer info data.
69 DWORD signer_info_size = 0;
70 bool result = !!CryptMsgGetParam(message.get(), CMSG_SIGNER_INFO_PARAM, 0,
71 nullptr, &signer_info_size);
72 if (!result)
73 return base::string16();
74
75 // Allocate enough space to hold the signer info.
76 std::unique_ptr<BYTE[]> signer_info_buffer(new BYTE[signer_info_size]);
77 CMSG_SIGNER_INFO* signer_info =
78 reinterpret_cast<CMSG_SIGNER_INFO*>(signer_info_buffer.get());
79
80 // Obtain the signer info.
81 result = !!CryptMsgGetParam(message.get(), CMSG_SIGNER_INFO_PARAM, 0,
82 signer_info, &signer_info_size);
83 if (!result)
84 return base::string16();
85
86 // Search for the signer certificate.
87 CERT_INFO CertInfo = {0};
88 PCCERT_CONTEXT cert_context = nullptr;
89 CertInfo.Issuer = signer_info->Issuer;
90 CertInfo.SerialNumber = signer_info->SerialNumber;
91
92 cert_context = CertFindCertificateInStore(
93 store.get(), X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
94 CERT_FIND_SUBJECT_CERT, &CertInfo, nullptr);
95 if (!cert_context)
96 return base::string16();
97
98 // Determine the size of the Subject name.
99 DWORD subject_name_size = CertGetNameString(
100 cert_context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, nullptr, 0);
101 if (!subject_name_size)
102 return base::string16();
103
104 base::string16 subject_name;
105 subject_name.resize(subject_name_size);
106
107 // Get subject name.
108 if (!(CertGetNameString(cert_context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
109 nullptr, const_cast<LPWSTR>(subject_name.c_str()),
110 subject_name_size))) {
111 return base::string16();
112 }
113
114 // The subject name is normalized because it can contain trailing null
115 // characters.
116 internal::NormalizeCertificateSubject(&subject_name);
117
118 return subject_name;
119 }
120
121 // Helper for scoped tracking a catalog admin context.
122 struct CryptCATContextScopedTraits {
InvalidValue__anon45171b180111::CryptCATContextScopedTraits123 static PVOID InvalidValue() { return nullptr; }
Free__anon45171b180111::CryptCATContextScopedTraits124 static void Free(PVOID context) { CryptCATAdminReleaseContext(context, 0); }
125 };
126 using ScopedCryptCATContext =
127 base::ScopedGeneric<PVOID, CryptCATContextScopedTraits>;
128
129 // Helper for scoped tracking of a catalog context. A catalog context is only
130 // valid with an associated admin context, so this is effectively a std::pair.
131 // A custom operator!= is required in order for a null |catalog_context| but
132 // non-null |context| to compare equal to the InvalidValue exposed by the
133 // traits class.
134 class CryptCATCatalogContext {
135 public:
CryptCATCatalogContext(PVOID context,PVOID catalog_context)136 CryptCATCatalogContext(PVOID context, PVOID catalog_context)
137 : context_(context), catalog_context_(catalog_context) {}
138
operator !=(const CryptCATCatalogContext & rhs) const139 bool operator!=(const CryptCATCatalogContext& rhs) const {
140 return catalog_context_ != rhs.catalog_context_;
141 }
142
context() const143 PVOID context() const { return context_; }
catalog_context() const144 PVOID catalog_context() const { return catalog_context_; }
145
146 private:
147 PVOID context_;
148 PVOID catalog_context_;
149 };
150
151 struct CryptCATCatalogContextScopedTraits {
InvalidValue__anon45171b180111::CryptCATCatalogContextScopedTraits152 static CryptCATCatalogContext InvalidValue() {
153 return CryptCATCatalogContext(nullptr, nullptr);
154 }
Free__anon45171b180111::CryptCATCatalogContextScopedTraits155 static void Free(const CryptCATCatalogContext& c) {
156 CryptCATAdminReleaseCatalogContext(c.context(), c.catalog_context(), 0);
157 }
158 };
159 using ScopedCryptCATCatalogContext =
160 base::ScopedGeneric<CryptCATCatalogContext,
161 CryptCATCatalogContextScopedTraits>;
162
163 // Extracts the subject name and catalog path if the provided file is present in
164 // a catalog file.
GetCatalogCertificateInfo(const base::FilePath & filename,CertificateInfo * certificate_info)165 void GetCatalogCertificateInfo(const base::FilePath& filename,
166 CertificateInfo* certificate_info) {
167 // Get a crypt context for signature verification.
168 ScopedCryptCATContext context;
169 {
170 PVOID raw_context = nullptr;
171 if (!CryptCATAdminAcquireContext(&raw_context, nullptr, 0))
172 return;
173 context.reset(raw_context);
174 }
175
176 // Open the file of interest.
177 base::win::ScopedHandle file_handle(
178 CreateFileW(filename.value().c_str(), GENERIC_READ,
179 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
180 nullptr, OPEN_EXISTING, 0, nullptr));
181 if (!file_handle.IsValid())
182 return;
183
184 // Get the size we need for our hash.
185 DWORD hash_size = 0;
186 CryptCATAdminCalcHashFromFileHandle(file_handle.Get(), &hash_size, nullptr,
187 0);
188 if (hash_size == 0)
189 return;
190
191 // Calculate the hash. If this fails then bail.
192 std::vector<BYTE> buffer(hash_size);
193 if (!CryptCATAdminCalcHashFromFileHandle(file_handle.Get(), &hash_size,
194 buffer.data(), 0)) {
195 return;
196 }
197
198 // Get catalog for our context.
199 ScopedCryptCATCatalogContext catalog_context(CryptCATCatalogContext(
200 context.get(), CryptCATAdminEnumCatalogFromHash(
201 context.get(), buffer.data(), hash_size, 0, nullptr)));
202 if (!catalog_context.is_valid())
203 return;
204
205 // Get the catalog info. This includes the path to the catalog itself, which
206 // contains the signature of interest.
207 CATALOG_INFO catalog_info = {};
208 catalog_info.cbStruct = sizeof(catalog_info);
209 if (!CryptCATCatalogInfoFromContext(catalog_context.get().catalog_context(),
210 &catalog_info, 0)) {
211 return;
212 }
213
214 // Attempt to get the "Subject" field from the signature of the catalog file
215 // itself.
216 base::FilePath catalog_path(catalog_info.wszCatalogFile);
217 base::string16 subject = GetSubjectNameInFile(catalog_path);
218
219 if (subject.empty())
220 return;
221
222 certificate_info->type = CertificateInfo::Type::CERTIFICATE_IN_CATALOG;
223 certificate_info->path = catalog_path;
224 certificate_info->subject = subject;
225 }
226
227 } // namespace
228
229 const wchar_t kClassIdRegistryKeyFormat[] = L"CLSID\\%ls\\InProcServer32";
230
231 // ModuleDatabase::CertificateInfo ---------------------------------------------
232
CertificateInfo()233 CertificateInfo::CertificateInfo() : type(Type::NO_CERTIFICATE) {}
234
235 // Extracts information about the certificate of the given file, if any is
236 // found.
GetCertificateInfo(const base::FilePath & filename,CertificateInfo * certificate_info)237 void GetCertificateInfo(const base::FilePath& filename,
238 CertificateInfo* certificate_info) {
239 DCHECK_EQ(CertificateInfo::Type::NO_CERTIFICATE, certificate_info->type);
240 DCHECK(certificate_info->path.empty());
241 DCHECK(certificate_info->subject.empty());
242
243 GetCatalogCertificateInfo(filename, certificate_info);
244 if (certificate_info->type == CertificateInfo::Type::CERTIFICATE_IN_CATALOG)
245 return;
246
247 base::string16 subject = GetSubjectNameInFile(filename);
248 if (subject.empty())
249 return;
250
251 certificate_info->type = CertificateInfo::Type::CERTIFICATE_IN_FILE;
252 certificate_info->path = filename;
253 certificate_info->subject = subject;
254 }
255
IsMicrosoftModule(base::StringPiece16 subject)256 bool IsMicrosoftModule(base::StringPiece16 subject) {
257 static constexpr wchar_t kMicrosoft[] = L"Microsoft ";
258 return base::StartsWith(subject, kMicrosoft);
259 }
260
GetEnvironmentVariablesMapping(const std::vector<base::string16> & environment_variables)261 StringMapping GetEnvironmentVariablesMapping(
262 const std::vector<base::string16>& environment_variables) {
263 std::unique_ptr<base::Environment> environment(base::Environment::Create());
264
265 StringMapping string_mapping;
266 for (const base::string16& variable : environment_variables) {
267 std::string value;
268 if (environment->GetVar(base::UTF16ToASCII(variable).c_str(), &value)) {
269 value = base::TrimString(value, "\\", base::TRIM_TRAILING).as_string();
270 string_mapping.push_back(
271 std::make_pair(base::i18n::ToLower(base::UTF8ToUTF16(value)),
272 L"%" + base::i18n::ToLower(variable) + L"%"));
273 }
274 }
275
276 return string_mapping;
277 }
278
CollapseMatchingPrefixInPath(const StringMapping & prefix_mapping,base::string16 * path)279 void CollapseMatchingPrefixInPath(const StringMapping& prefix_mapping,
280 base::string16* path) {
281 const base::string16 path_copy = *path;
282 DCHECK_EQ(base::i18n::ToLower(path_copy), path_copy);
283
284 size_t min_length = std::numeric_limits<size_t>::max();
285 for (const auto& mapping : prefix_mapping) {
286 DCHECK_EQ(base::i18n::ToLower(mapping.first), mapping.first);
287 if (base::StartsWith(path_copy, mapping.first)) {
288 // Make sure the matching prefix is a full path component.
289 if (path_copy[mapping.first.length()] != '\\' &&
290 path_copy[mapping.first.length()] != '\0') {
291 continue;
292 }
293
294 base::string16 collapsed_path = path_copy;
295 base::ReplaceFirstSubstringAfterOffset(&collapsed_path, 0, mapping.first,
296 mapping.second);
297 size_t length = collapsed_path.length() - mapping.second.length();
298 if (length < min_length) {
299 *path = collapsed_path;
300 min_length = length;
301 }
302 }
303 }
304 }
305
GetModuleImageSizeAndTimeDateStamp(const base::FilePath & path,uint32_t * size_of_image,uint32_t * time_date_stamp)306 bool GetModuleImageSizeAndTimeDateStamp(const base::FilePath& path,
307 uint32_t* size_of_image,
308 uint32_t* time_date_stamp) {
309 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
310 if (!file.IsValid())
311 return false;
312
313 // The values fetched here from the NT header live in the first 4k bytes of
314 // the file in a well-formed dll.
315 constexpr size_t kPageSize = 4096;
316
317 // Note: std::make_unique() is explicitly avoided because it does value-
318 // initialization on arrays, which is not needed in this case.
319 auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[kPageSize]);
320 int bytes_read =
321 file.Read(0, reinterpret_cast<char*>(buffer.get()), kPageSize);
322 if (bytes_read == -1)
323 return false;
324
325 base::win::PeImageReader pe_image_reader;
326 if (!pe_image_reader.Initialize(buffer.get(), bytes_read))
327 return false;
328
329 *size_of_image = pe_image_reader.GetSizeOfImage();
330 *time_date_stamp = pe_image_reader.GetCoffFileHeader()->TimeDateStamp;
331
332 return true;
333 }
334
335 namespace internal {
336
NormalizeCertificateSubject(base::string16 * subject)337 void NormalizeCertificateSubject(base::string16* subject) {
338 size_t first_null = subject->find(L'\0');
339 if (first_null != base::string16::npos)
340 subject->resize(first_null);
341 }
342
343 } // namespace internal
344