1 // Copyright 2014 The Chromium Authors
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 "base/win/pe_image_reader.h"
6
7 #include <wintrust.h>
8
9 #include <memory>
10
11 #include "base/check_op.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/numerics/safe_math.h"
14
15 namespace base {
16 namespace win {
17
18 // A class template of traits pertaining to IMAGE_OPTIONAL_HEADER{32,64}.
19 template <class HEADER_TYPE>
20 struct OptionalHeaderTraits {};
21
22 template <>
23 struct OptionalHeaderTraits<IMAGE_OPTIONAL_HEADER32> {
24 static const PeImageReader::WordSize word_size = PeImageReader::WORD_SIZE_32;
25 };
26
27 template <>
28 struct OptionalHeaderTraits<IMAGE_OPTIONAL_HEADER64> {
29 static const PeImageReader::WordSize word_size = PeImageReader::WORD_SIZE_64;
30 };
31
32 // A template for type-specific optional header implementations. This, in
33 // conjunction with the OptionalHeader interface, effectively erases the
34 // underlying structure type from the point of view of the PeImageReader.
35 template <class OPTIONAL_HEADER_TYPE>
36 class PeImageReader::OptionalHeaderImpl : public PeImageReader::OptionalHeader {
37 public:
38 using TraitsType = OptionalHeaderTraits<OPTIONAL_HEADER_TYPE>;
39
OptionalHeaderImpl(const uint8_t * optional_header_start)40 explicit OptionalHeaderImpl(const uint8_t* optional_header_start)
41 : optional_header_(reinterpret_cast<const OPTIONAL_HEADER_TYPE*>(
42 optional_header_start)) {}
43
44 OptionalHeaderImpl(const OptionalHeaderImpl&) = delete;
45 OptionalHeaderImpl& operator=(const OptionalHeaderImpl&) = delete;
46
GetWordSize()47 WordSize GetWordSize() override { return TraitsType::word_size; }
48
GetDataDirectoryOffset()49 size_t GetDataDirectoryOffset() override {
50 return offsetof(OPTIONAL_HEADER_TYPE, DataDirectory);
51 }
52
GetDataDirectorySize()53 DWORD GetDataDirectorySize() override {
54 return optional_header_->NumberOfRvaAndSizes;
55 }
56
GetDataDirectoryEntries()57 const IMAGE_DATA_DIRECTORY* GetDataDirectoryEntries() override {
58 return &optional_header_->DataDirectory[0];
59 }
60
GetSizeOfImage()61 DWORD GetSizeOfImage() override { return optional_header_->SizeOfImage; }
62
63 private:
64 raw_ptr<const OPTIONAL_HEADER_TYPE> optional_header_;
65 };
66
PeImageReader()67 PeImageReader::PeImageReader() {}
68
~PeImageReader()69 PeImageReader::~PeImageReader() {
70 Clear();
71 }
72
Initialize(const uint8_t * image_data,size_t image_size)73 bool PeImageReader::Initialize(const uint8_t* image_data, size_t image_size) {
74 image_data_ = image_data;
75 image_size_ = image_size;
76
77 if (!ValidateDosHeader() || !ValidatePeSignature() ||
78 !ValidateCoffFileHeader() || !ValidateOptionalHeader() ||
79 !ValidateSectionHeaders()) {
80 Clear();
81 return false;
82 }
83
84 return true;
85 }
86
GetWordSize()87 PeImageReader::WordSize PeImageReader::GetWordSize() {
88 return optional_header_->GetWordSize();
89 }
90
GetDosHeader()91 const IMAGE_DOS_HEADER* PeImageReader::GetDosHeader() {
92 DCHECK_NE((validation_state_ & VALID_DOS_HEADER), 0U);
93 return reinterpret_cast<const IMAGE_DOS_HEADER*>(image_data_);
94 }
95
GetCoffFileHeader()96 const IMAGE_FILE_HEADER* PeImageReader::GetCoffFileHeader() {
97 DCHECK_NE((validation_state_ & VALID_COFF_FILE_HEADER), 0U);
98 return reinterpret_cast<const IMAGE_FILE_HEADER*>(
99 image_data_ + GetDosHeader()->e_lfanew + sizeof(DWORD));
100 }
101
GetOptionalHeaderData(size_t * optional_header_size)102 const uint8_t* PeImageReader::GetOptionalHeaderData(
103 size_t* optional_header_size) {
104 *optional_header_size = GetOptionalHeaderSize();
105 return GetOptionalHeaderStart();
106 }
107
GetNumberOfSections()108 size_t PeImageReader::GetNumberOfSections() {
109 return GetCoffFileHeader()->NumberOfSections;
110 }
111
GetSectionHeaderAt(size_t index)112 const IMAGE_SECTION_HEADER* PeImageReader::GetSectionHeaderAt(size_t index) {
113 DCHECK_NE((validation_state_ & VALID_SECTION_HEADERS), 0U);
114 DCHECK_LT(index, GetNumberOfSections());
115 return reinterpret_cast<const IMAGE_SECTION_HEADER*>(
116 GetOptionalHeaderStart() + GetOptionalHeaderSize() +
117 (sizeof(IMAGE_SECTION_HEADER) * index));
118 }
119
GetExportSection(size_t * section_size)120 const uint8_t* PeImageReader::GetExportSection(size_t* section_size) {
121 size_t data_size = 0;
122 const uint8_t* data = GetImageData(IMAGE_DIRECTORY_ENTRY_EXPORT, &data_size);
123
124 // The export section data must be big enough for the export directory.
125 if (!data || data_size < sizeof(IMAGE_EXPORT_DIRECTORY))
126 return nullptr;
127
128 *section_size = data_size;
129 return data;
130 }
131
GetNumberOfDebugEntries()132 size_t PeImageReader::GetNumberOfDebugEntries() {
133 size_t data_size = 0;
134 const uint8_t* data = GetImageData(IMAGE_DIRECTORY_ENTRY_DEBUG, &data_size);
135 return data ? (data_size / sizeof(IMAGE_DEBUG_DIRECTORY)) : 0;
136 }
137
GetDebugEntry(size_t index,const uint8_t ** raw_data,size_t * raw_data_size)138 const IMAGE_DEBUG_DIRECTORY* PeImageReader::GetDebugEntry(
139 size_t index,
140 const uint8_t** raw_data,
141 size_t* raw_data_size) {
142 DCHECK_LT(index, GetNumberOfDebugEntries());
143
144 // Get the debug directory.
145 size_t debug_directory_size = 0;
146 const IMAGE_DEBUG_DIRECTORY* entries =
147 reinterpret_cast<const IMAGE_DEBUG_DIRECTORY*>(
148 GetImageData(IMAGE_DIRECTORY_ENTRY_DEBUG, &debug_directory_size));
149 if (!entries)
150 return nullptr;
151
152 const IMAGE_DEBUG_DIRECTORY& entry = entries[index];
153 const uint8_t* debug_data = nullptr;
154 if (GetStructureAt(entry.PointerToRawData, entry.SizeOfData, &debug_data)) {
155 *raw_data = debug_data;
156 *raw_data_size = entry.SizeOfData;
157 }
158 return &entry;
159 }
160
EnumCertificates(EnumCertificatesCallback callback,void * context)161 bool PeImageReader::EnumCertificates(EnumCertificatesCallback callback,
162 void* context) {
163 size_t data_size = 0;
164 const uint8_t* data =
165 GetImageData(IMAGE_DIRECTORY_ENTRY_SECURITY, &data_size);
166 if (!data)
167 return false; // Certificate table is out of bounds.
168 const size_t kWinCertificateSize = offsetof(WIN_CERTIFICATE, bCertificate);
169 while (data_size) {
170 const WIN_CERTIFICATE* win_certificate =
171 reinterpret_cast<const WIN_CERTIFICATE*>(data);
172 if (kWinCertificateSize > data_size ||
173 kWinCertificateSize > win_certificate->dwLength ||
174 win_certificate->dwLength > data_size) {
175 return false;
176 }
177 if (!(*callback)(
178 win_certificate->wRevision, win_certificate->wCertificateType,
179 &win_certificate->bCertificate[0],
180 win_certificate->dwLength - kWinCertificateSize, context)) {
181 return false;
182 }
183 size_t padded_length = (win_certificate->dwLength + 7) & ~0x7u;
184 // Don't overflow when recalculating data_size, since padded_length can be
185 // attacker controlled.
186 if (!CheckSub(data_size, padded_length).AssignIfValid(&data_size))
187 return false;
188 data += padded_length;
189 }
190 return true;
191 }
192
GetSizeOfImage()193 DWORD PeImageReader::GetSizeOfImage() {
194 return optional_header_->GetSizeOfImage();
195 }
196
Clear()197 void PeImageReader::Clear() {
198 image_data_ = nullptr;
199 image_size_ = 0;
200 validation_state_ = 0;
201 optional_header_.reset();
202 }
203
ValidateDosHeader()204 bool PeImageReader::ValidateDosHeader() {
205 const IMAGE_DOS_HEADER* dos_header = nullptr;
206 if (!GetStructureAt(0, &dos_header) ||
207 dos_header->e_magic != IMAGE_DOS_SIGNATURE || dos_header->e_lfanew < 0) {
208 return false;
209 }
210
211 validation_state_ |= VALID_DOS_HEADER;
212 return true;
213 }
214
ValidatePeSignature()215 bool PeImageReader::ValidatePeSignature() {
216 const DWORD* signature = nullptr;
217 if (!GetStructureAt(static_cast<size_t>(GetDosHeader()->e_lfanew),
218 &signature) ||
219 *signature != IMAGE_NT_SIGNATURE) {
220 return false;
221 }
222
223 validation_state_ |= VALID_PE_SIGNATURE;
224 return true;
225 }
226
ValidateCoffFileHeader()227 bool PeImageReader::ValidateCoffFileHeader() {
228 DCHECK_NE((validation_state_ & VALID_PE_SIGNATURE), 0U);
229 const IMAGE_FILE_HEADER* file_header = nullptr;
230 if (!GetStructureAt(static_cast<size_t>(GetDosHeader()->e_lfanew) +
231 offsetof(IMAGE_NT_HEADERS32, FileHeader),
232 &file_header)) {
233 return false;
234 }
235
236 validation_state_ |= VALID_COFF_FILE_HEADER;
237 return true;
238 }
239
ValidateOptionalHeader()240 bool PeImageReader::ValidateOptionalHeader() {
241 const IMAGE_FILE_HEADER* file_header = GetCoffFileHeader();
242 const size_t optional_header_offset =
243 static_cast<size_t>(GetDosHeader()->e_lfanew) +
244 offsetof(IMAGE_NT_HEADERS32, OptionalHeader);
245 const size_t optional_header_size = file_header->SizeOfOptionalHeader;
246 const WORD* optional_header_magic = nullptr;
247
248 if (optional_header_size < sizeof(*optional_header_magic) ||
249 !GetStructureAt(optional_header_offset, &optional_header_magic)) {
250 return false;
251 }
252
253 std::unique_ptr<OptionalHeader> optional_header;
254 if (*optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
255 optional_header =
256 std::make_unique<OptionalHeaderImpl<IMAGE_OPTIONAL_HEADER32>>(
257 image_data_ + optional_header_offset);
258 } else if (*optional_header_magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
259 optional_header =
260 std::make_unique<OptionalHeaderImpl<IMAGE_OPTIONAL_HEADER64>>(
261 image_data_ + optional_header_offset);
262 } else {
263 return false;
264 }
265
266 // Does all of the claimed optional header fit in the image?
267 if (optional_header_size > image_size_ - optional_header_offset)
268 return false;
269
270 // Is the claimed optional header big enough for everything but the dir?
271 if (optional_header->GetDataDirectoryOffset() > optional_header_size)
272 return false;
273
274 // Is there enough room for all of the claimed directory entries?
275 if (optional_header->GetDataDirectorySize() >
276 ((optional_header_size - optional_header->GetDataDirectoryOffset()) /
277 sizeof(IMAGE_DATA_DIRECTORY))) {
278 return false;
279 }
280
281 optional_header_.swap(optional_header);
282 validation_state_ |= VALID_OPTIONAL_HEADER;
283 return true;
284 }
285
ValidateSectionHeaders()286 bool PeImageReader::ValidateSectionHeaders() {
287 const uint8_t* first_section_header =
288 GetOptionalHeaderStart() + GetOptionalHeaderSize();
289 const size_t number_of_sections = GetNumberOfSections();
290
291 // Do all section headers fit in the image?
292 if (!GetStructureAt(static_cast<size_t>(first_section_header - image_data_),
293 number_of_sections * sizeof(IMAGE_SECTION_HEADER),
294 &first_section_header)) {
295 return false;
296 }
297
298 validation_state_ |= VALID_SECTION_HEADERS;
299 return true;
300 }
301
GetOptionalHeaderStart()302 const uint8_t* PeImageReader::GetOptionalHeaderStart() {
303 DCHECK_NE((validation_state_ & VALID_OPTIONAL_HEADER), 0U);
304 return (image_data_ + GetDosHeader()->e_lfanew +
305 offsetof(IMAGE_NT_HEADERS32, OptionalHeader));
306 }
307
GetOptionalHeaderSize()308 size_t PeImageReader::GetOptionalHeaderSize() {
309 return GetCoffFileHeader()->SizeOfOptionalHeader;
310 }
311
GetDataDirectoryEntryAt(size_t index)312 const IMAGE_DATA_DIRECTORY* PeImageReader::GetDataDirectoryEntryAt(
313 size_t index) {
314 DCHECK_NE((validation_state_ & VALID_OPTIONAL_HEADER), 0U);
315 if (index >= optional_header_->GetDataDirectorySize())
316 return nullptr;
317 return &optional_header_->GetDataDirectoryEntries()[index];
318 }
319
FindSectionFromRva(uint32_t relative_address)320 const IMAGE_SECTION_HEADER* PeImageReader::FindSectionFromRva(
321 uint32_t relative_address) {
322 const size_t number_of_sections = GetNumberOfSections();
323 for (size_t i = 0; i < number_of_sections; ++i) {
324 const IMAGE_SECTION_HEADER* section_header = GetSectionHeaderAt(i);
325 // Is the raw data present in the image? If no, optimistically keep looking.
326 const uint8_t* section_data = nullptr;
327 if (!GetStructureAt(section_header->PointerToRawData,
328 section_header->SizeOfRawData, §ion_data)) {
329 continue;
330 }
331 // Does the RVA lie on or after this section's start when mapped? If no,
332 // bail.
333 if (section_header->VirtualAddress > relative_address)
334 break;
335 // Does the RVA lie within the section when mapped? If no, keep looking.
336 size_t address_offset = relative_address - section_header->VirtualAddress;
337 if (address_offset > section_header->Misc.VirtualSize)
338 continue;
339 // We have a winner.
340 return section_header;
341 }
342 return nullptr;
343 }
344
GetImageData(size_t index,size_t * data_length)345 const uint8_t* PeImageReader::GetImageData(size_t index, size_t* data_length) {
346 // Get the requested directory entry.
347 const IMAGE_DATA_DIRECTORY* entry = GetDataDirectoryEntryAt(index);
348 if (!entry)
349 return nullptr;
350
351 // The entry for the certificate table is special in that its address is a
352 // file pointer rather than an RVA.
353 if (index == IMAGE_DIRECTORY_ENTRY_SECURITY) {
354 // Does the data fit within the file.
355 if (entry->VirtualAddress > image_size_ ||
356 image_size_ - entry->VirtualAddress < entry->Size) {
357 return nullptr;
358 }
359 *data_length = entry->Size;
360 return image_data_ + entry->VirtualAddress;
361 }
362
363 // Find the section containing the data.
364 const IMAGE_SECTION_HEADER* header =
365 FindSectionFromRva(entry->VirtualAddress);
366 if (!header)
367 return nullptr;
368
369 // Does the data fit within the section when mapped?
370 size_t data_offset = entry->VirtualAddress - header->VirtualAddress;
371 if (entry->Size > (header->Misc.VirtualSize - data_offset))
372 return nullptr;
373
374 // Is the data entirely present on disk (if not it's zeroed out when loaded)?
375 if (data_offset >= header->SizeOfRawData ||
376 header->SizeOfRawData - data_offset < entry->Size) {
377 return nullptr;
378 }
379
380 *data_length = entry->Size;
381 return image_data_ + header->PointerToRawData + data_offset;
382 }
383
384 } // namespace win
385 } // namespace base
386