1 : // Copyright 2012 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 : //
15 : // ZapTimestamps uses PEFile/ImageLayout/BlockGraph to represent a PE file in
16 : // memory, and TypedBlock to navigate through the PE structures of the file.
17 : // We don't use a full decomposition of the image, but rather only decompose
18 : // the PE headers and structures. As such, ZapTimetamps can be seen as a
19 : // lightweight decomposer. It would be better do this directly using the
20 : // internal intermediate representation formats of PEFileParser, but this
21 : // functionality would require some refactoring.
22 : //
23 : // Changes that are required to be made to the PE file are represented by an
24 : // address space, mapping replacement data to file offsets. This address-space
25 : // can then be simply 'stamped' on to the PE file to be modified.
26 : //
27 : // The matching PDB file is completely rewritten to guarantee that it is
28 : // canonical (as long as the underlying PdbWriter doesn't change). We load all
29 : // of the streams into memory, reach in and make local modifications, and
30 : // rewrite the entire file to disk.
31 :
32 : #include "syzygy/zap_timestamp/zap_timestamp.h"
33 :
34 : #include "base/at_exit.h"
35 : #include "base/bind.h"
36 : #include "base/command_line.h"
37 : #include "base/file_util.h"
38 : #include "base/logging.h"
39 : #include "base/md5.h"
40 : #include "base/stringprintf.h"
41 : #include "base/files/scoped_temp_dir.h"
42 : #include "syzygy/block_graph/typed_block.h"
43 : #include "syzygy/pdb/pdb_byte_stream.h"
44 : #include "syzygy/pdb/pdb_constants.h"
45 : #include "syzygy/pdb/pdb_reader.h"
46 : #include "syzygy/pdb/pdb_util.h"
47 : #include "syzygy/pdb/pdb_writer.h"
48 : #include "syzygy/pe/find.h"
49 : #include "syzygy/pe/pdb_info.h"
50 : #include "syzygy/pe/pe_data.h"
51 : #include "syzygy/pe/pe_file_parser.h"
52 : #include "syzygy/pe/pe_file_writer.h"
53 :
54 : namespace zap_timestamp {
55 :
56 : namespace {
57 :
58 : using block_graph::BlockGraph;
59 : using block_graph::ConstTypedBlock;
60 : using core::FileOffsetAddress;
61 : using core::RelativeAddress;
62 : using pdb::PdbByteStream;
63 : using pdb::PdbFile;
64 : using pdb::PdbReader;
65 : using pdb::PdbStream;
66 : using pdb::PdbWriter;
67 : using pdb::WritablePdbStream;
68 : using pe::ImageLayout;
69 : using pe::PEFile;
70 : using pe::PEFileParser;
71 :
72 : typedef ConstTypedBlock<IMAGE_DEBUG_DIRECTORY> ImageDebugDirectory;
73 : typedef ConstTypedBlock<IMAGE_DOS_HEADER> DosHeader;
74 : typedef ConstTypedBlock<IMAGE_NT_HEADERS> NtHeaders;
75 : typedef ConstTypedBlock<pe::CvInfoPdb70> CvInfoPdb;
76 : typedef ZapTimestamp::PatchAddressSpace PatchAddressSpace;
77 : typedef ZapTimestamp::PatchData PatchData;
78 :
79 : const char kUsageFormatStr[] =
80 : "Usage: %ls <PE files>\n"
81 : "\n"
82 : " A tool that normalizes the GUID and timestamps associated with a given\n"
83 : " PE/PDB file pair. The PDB files matching each given PE file will be\n"
84 : " tracked down automatically.\n";
85 :
86 : void PrintUsage(FILE* out,
87 : const base::FilePath& program,
88 : const base::StringPiece& message) {
89 : if (!message.empty()) {
90 : ::fwrite(message.data(), 1, message.length(), out);
91 : ::fprintf(out, "\n\n");
92 : }
93 :
94 : ::fprintf(out, kUsageFormatStr, program.BaseName().value().c_str());
95 : }
96 :
97 : // An intermediate reference type used to track references generated by
98 : // PEFileParser.
99 : struct IntermediateReference {
100 : BlockGraph::ReferenceType type;
101 : BlockGraph::Size size;
102 : RelativeAddress address;
103 : };
104 :
105 : // A map of intermediate references. This tracks references created by the
106 : // PEFileParser.
107 : typedef std::map<RelativeAddress, IntermediateReference>
108 : IntermediateReferenceMap;
109 :
110 : // Adds a reference to the given intermediate reference map. Used as a callback
111 : // to PEFileParser.
112 : bool AddReference(IntermediateReferenceMap* references,
113 : RelativeAddress source,
114 : BlockGraph::ReferenceType type,
115 : BlockGraph::Size size,
116 E : RelativeAddress destination) {
117 E : DCHECK(references != NULL);
118 :
119 E : IntermediateReference ref = { type, size, destination };
120 E : return references->insert(std::make_pair(source, ref)).second;
121 E : }
122 :
123 : // Returns the block and offset into the block associated with the given
124 : // address and size. Sets block pointer to NULL and offset to 0 if no block
125 : // was found for the address and size. Returns true if a block is found, false
126 : // otherwise.
127 : bool LookupBlockOffset(const ImageLayout& image_layout,
128 : RelativeAddress address,
129 : size_t size,
130 : BlockGraph::Block** block,
131 E : BlockGraph::Offset* offset) {
132 E : DCHECK(block != NULL);
133 E : DCHECK(offset != NULL);
134 :
135 E : *block = image_layout.blocks.GetContainingBlock(address, size);
136 E : if (*block == NULL) {
137 E : *offset = 0;
138 E : return false;
139 : }
140 :
141 E : *offset = address - (*block)->addr();
142 E : return true;
143 E : }
144 :
145 : // Performs a decomposition of the given PE file, only parsing out the PE
146 : // data blocks and references between them.
147 : bool MiniDecompose(const PEFile& pe_file,
148 : ImageLayout* image_layout,
149 E : BlockGraph::Block** dos_header_block) {
150 E : DCHECK(image_layout != NULL);
151 E : DCHECK(dos_header_block != NULL);
152 :
153 E : IntermediateReferenceMap references;
154 :
155 : PEFileParser::AddReferenceCallback add_reference = base::Bind(
156 E : &AddReference, base::Unretained(&references));
157 E : PEFileParser pe_file_parser(pe_file, &image_layout->blocks, add_reference);
158 :
159 E : PEFileParser::PEHeader pe_header;
160 E : if (!pe_file_parser.ParseImage(&pe_header)) {
161 i : LOG(ERROR) << "Failed to parse PE file: " << pe_file.path().value();
162 i : return false;
163 : }
164 :
165 E : if (!pe::CopyHeaderToImageLayout(pe_header.nt_headers, image_layout)) {
166 i : LOG(ERROR) << "Failed to copy NT headers to image layout.";
167 i : return false;
168 : }
169 :
170 : // Finalize the intermediate references. We only finalize those that are
171 : // within the closed set of blocks.
172 E : IntermediateReferenceMap::const_iterator ref_it = references.begin();
173 E : for (; ref_it != references.end(); ++ref_it) {
174 E : BlockGraph::Block* src_block = NULL;
175 E : BlockGraph::Offset src_offset = 0;
176 : if (!LookupBlockOffset(*image_layout, ref_it->first, ref_it->second.size,
177 E : &src_block, &src_offset)) {
178 E : continue;
179 : }
180 :
181 E : BlockGraph::Block* dst_block = NULL;
182 E : BlockGraph::Offset dst_offset = 0;
183 : if (!LookupBlockOffset(*image_layout, ref_it->second.address, 1,
184 E : &dst_block, &dst_offset)) {
185 E : continue;
186 : }
187 :
188 : // Make the final reference.
189 : BlockGraph::Reference ref(ref_it->second.type, ref_it->second.size,
190 E : dst_block, dst_offset, dst_offset);
191 E : CHECK(src_block->SetReference(src_offset, ref));
192 E : }
193 :
194 E : *dos_header_block = pe_header.dos_header;
195 :
196 E : return true;
197 E : }
198 :
199 : // Marks the range of data at @p rel_addr and of size @p size as needing to be
200 : // changed. It will be replaced with the data in @p data, and marked with the
201 : // description @p name (for debugging purposes). The change is recorded in the
202 : // provided PatchAddressSpace @p file_addr_space in terms of file offsets.
203 : // This performs the necessary address space translations via @p pe_file and
204 : // ensures that the change does not conflict with any other required changes.
205 : bool MarkData(const PEFile& pe_file,
206 : RelativeAddress rel_addr,
207 : size_t size,
208 : const uint8* data,
209 : const base::StringPiece& name,
210 E : PatchAddressSpace* file_addr_space) {
211 E : DCHECK(file_addr_space);
212 :
213 E : FileOffsetAddress file_addr;
214 E : if (!pe_file.Translate(rel_addr, &file_addr)) {
215 i : LOG(ERROR) << "Failed to translate " << rel_addr << " to file offset.";
216 i : return false;
217 : }
218 :
219 : if (!file_addr_space->Insert(PatchAddressSpace::Range(file_addr, size),
220 E : PatchData(data, name))) {
221 i : LOG(ERROR) << "Failed to insert file range at " << file_addr
222 : << " of length " << size << ".";
223 i : return false;
224 : }
225 :
226 E : return true;
227 E : }
228 :
229 : // Given a data directory of type T containing a member variable named
230 : // TimeDateStamp, this will mark the timestamp for changing to the value
231 : // provided in @p timestamp_data. The change will be recorded in the provided
232 : // PatchAddressSpace @p file_addr_space.
233 : template<typename T>
234 : bool MarkDataDirectoryTimestamps(const PEFile& pe_file,
235 : NtHeaders& nt_headers,
236 : size_t data_dir_index,
237 : const char* data_dir_name,
238 : const uint8* timestamp_data,
239 E : PatchAddressSpace* file_addr_space) {
240 E : DCHECK_GT(arraysize(nt_headers->OptionalHeader.DataDirectory),
241 : data_dir_index);
242 E : DCHECK(timestamp_data != NULL);
243 E : DCHECK(file_addr_space != NULL);
244 :
245 : // It is not an error if the debug directory doesn't exist.
246 : const IMAGE_DATA_DIRECTORY& data_dir_info =
247 E : nt_headers->OptionalHeader.DataDirectory[data_dir_index];
248 E : if (!nt_headers.HasReference(data_dir_info.VirtualAddress)) {
249 i : DCHECK_EQ(0u, data_dir_info.VirtualAddress);
250 i : LOG(INFO) << "PE file contains no data directory " << data_dir_index << ".";
251 i : return true;
252 : }
253 :
254 E : ConstTypedBlock<T> data_dir;
255 E : if (!nt_headers.Dereference(data_dir_info.VirtualAddress, &data_dir)) {
256 i : LOG(ERROR) << "Failed to dereference data directory " << data_dir_index
257 : << ".";
258 i : return false;
259 : }
260 :
261 E : FileOffsetAddress data_dir_addr;
262 E : if (!pe_file.Translate(data_dir.block()->addr(), &data_dir_addr)) {
263 i : LOG(ERROR) << "Failed to locate data directory " << data_dir_index << ".";
264 i : return false;
265 : }
266 :
267 E : if (data_dir->TimeDateStamp == 0)
268 E : return true;
269 :
270 : FileOffsetAddress timestamp_addr = data_dir_addr +
271 E : data_dir.OffsetOf(data_dir->TimeDateStamp);
272 :
273 E : std::string name = base::StringPrintf("%s Timestamp", data_dir_name);
274 : if (!file_addr_space->Insert(PatchAddressSpace::Range(timestamp_addr,
275 : sizeof(DWORD)),
276 E : PatchData(timestamp_data, name))) {
277 i : LOG(ERROR) << "Failed to mark timestamp of data directory "
278 : << data_dir_index << ".";
279 i : return false;
280 : }
281 :
282 E : return true;
283 E : }
284 :
285 E : bool Md5Consume(size_t bytes, FILE* file, base::MD5Context* context) {
286 E : char buffer[4096] = { 0 };
287 :
288 E : size_t cur = 0;
289 E : while (cur < bytes) {
290 E : size_t bytes_to_read = std::min(bytes - cur, sizeof(buffer));
291 E : size_t bytes_read = ::fread(buffer, 1, bytes_to_read, file);
292 E : if (bytes_read != bytes_to_read) {
293 i : LOG(ERROR) << "Error reading from file (got " << bytes_read
294 : << ", expected " << bytes_to_read << ").";
295 i : return false;
296 : }
297 :
298 E : base::MD5Update(context, base::StringPiece(buffer, bytes_read));
299 E : cur += bytes_read;
300 E : }
301 E : DCHECK_EQ(cur, bytes);
302 :
303 E : return true;
304 E : }
305 :
306 : bool UpdateFileInPlace(const base::FilePath& path,
307 E : const PatchAddressSpace& updates) {
308 E : LOG(INFO) << "Patching file: " << path.value();
309 :
310 E : file_util::ScopedFILE file(file_util::OpenFile(path, "rb+"));
311 E : if (file.get() == NULL) {
312 i : LOG(ERROR) << "Unable to open file for updating: " << path.value();
313 i : return false;
314 : }
315 :
316 E : PatchAddressSpace::const_iterator it = updates.begin();
317 E : for (; it != updates.end(); ++it) {
318 : // No data? Then nothing to update. This happens for the PE checksum, which
319 : // has a NULL data pointer. We update it later on in another pass.
320 E : if (it->second.data == NULL)
321 E : continue;
322 :
323 E : LOG(INFO) << " Patching " << it->second.name << ", " << it->first.size()
324 : << " bytes at " << it->first.start();
325 :
326 : // Seek to the position to be updated.
327 E : if (::fseek(file.get(), it->first.start().value(), SEEK_SET) != 0) {
328 i : LOG(ERROR) << "Failed to seek to " << it->first.start() << " of file: "
329 : << path.value();
330 i : return false;
331 : }
332 :
333 : // Write the updated data.
334 : size_t bytes_written = ::fwrite(it->second.data, 1, it->first.size(),
335 E : file.get());
336 E : if (bytes_written != it->first.size()) {
337 i : LOG(ERROR) << "Failed to write " << it->first.size() << " bytes to "
338 : << "position " << it->first.start() << " of file: "
339 : << path.value();
340 : }
341 E : }
342 :
343 E : LOG(INFO) << "Finished patching file: " << path.value();
344 E : file.reset();
345 :
346 E : return true;
347 E : }
348 :
349 : // Ensures that the stream with the given ID is writable, returning a scoped
350 : // pointer to it.
351 : scoped_refptr<PdbStream> GetWritablePdbStream(size_t index,
352 E : PdbFile* pdb_file) {
353 E : DCHECK(pdb_file != NULL);
354 E : DCHECK_GT(pdb_file->StreamCount(), index);
355 :
356 E : scoped_refptr<PdbStream> reader = pdb_file->GetStream(index);
357 :
358 : // Try and get the writer. If it's not available, then replace the stream
359 : // with a byte stream, which is in-place writable.
360 E : scoped_refptr<WritablePdbStream> writer = reader->GetWritablePdbStream();
361 E : if (writer.get() == NULL) {
362 E : scoped_refptr<PdbByteStream> byte_stream(new PdbByteStream());
363 E : byte_stream->Init(reader);
364 E : pdb_file->ReplaceStream(index, byte_stream);
365 E : reader = byte_stream;
366 E : }
367 :
368 E : return reader;
369 E : }
370 :
371 E : void OutputSummaryStats(base::FilePath& path) {
372 E : file_util::ScopedFILE file(file_util::OpenFile(path, "rb"));
373 E : if (file.get() == NULL) {
374 i : LOG(ERROR) << "Unable to open file for reading: " << path.value();
375 i : return;
376 : }
377 E : ::fseek(file.get(), 0, SEEK_END);
378 E : size_t file_size = ::ftell(file.get());
379 E : ::fseek(file.get(), 0, SEEK_SET);
380 :
381 : base::MD5Context md5_context;
382 E : base::MD5Init(&md5_context);
383 E : if (!Md5Consume(file_size, file.get(), &md5_context))
384 i : return;
385 :
386 : base::MD5Digest md5_digest;
387 E : base::MD5Final(&md5_digest, &md5_context);
388 E : std::string md5_string = base::MD5DigestToBase16(md5_digest);
389 :
390 E : LOG(INFO) << "Path: " << path.value();
391 E : LOG(INFO) << " Size : " << file_size;
392 E : LOG(INFO) << " Digest: " << md5_string;
393 E : }
394 :
395 : bool NormalizeDbiStream(DWORD pdb_age_data,
396 E : PdbByteStream* dbi_stream) {
397 E : DCHECK(dbi_stream != NULL);
398 :
399 E : LOG(INFO) << "Updating PDB DBI stream.";
400 :
401 E : uint8* dbi_data = dbi_stream->data();
402 E : if (dbi_stream->length() < sizeof(pdb::DbiHeader)) {
403 i : LOG(ERROR) << "DBI stream too short.";
404 i : return false;
405 : }
406 E : pdb::DbiHeader* dbi_header = reinterpret_cast<pdb::DbiHeader*>(dbi_data);
407 :
408 : // Update the age in the DbiHeader as well. This needs to match pdb_age
409 : // in the PDB header.
410 E : dbi_header->age = pdb_age_data;
411 E : dbi_data += sizeof(*dbi_header);
412 :
413 : // Ensure that the module information is addressable.
414 E : if (dbi_stream->length() < dbi_header->gp_modi_size) {
415 i : LOG(ERROR) << "Invalid DBI header gp_modi_size.";
416 i : return false;
417 : }
418 :
419 : // Run over the module information.
420 : // TODO(chrisha): Use BufferWriter to do this. We need to update it to handle
421 : // type casts and bounds checking.
422 E : uint8* module_info_end = dbi_data + dbi_header->gp_modi_size;
423 E : while (dbi_data < module_info_end) {
424 : pdb::DbiModuleInfoBase* module_info =
425 E : reinterpret_cast<pdb::DbiModuleInfoBase*>(dbi_data);
426 E : module_info->offsets = 0;
427 E : dbi_data += sizeof(*module_info);
428 :
429 : // Skip two NULL terminated strings after the module info.
430 E : while (*dbi_data != 0) ++dbi_data;
431 E : ++dbi_data;
432 E : while (*dbi_data != 0) ++dbi_data;
433 E : ++dbi_data;
434 :
435 : // Skip until we're at a multiple of 4 position.
436 E : size_t offset = dbi_data - dbi_stream->data();
437 E : offset = ((offset + 3) / 4) * 4;
438 E : dbi_data = dbi_stream->data() + offset;
439 E : }
440 :
441 : // Ensure that the section contributions are addressable.
442 : size_t section_contrib_end_pos = dbi_header->gp_modi_size + sizeof(uint32) +
443 E : dbi_header->section_contribution_size;
444 E : if (dbi_stream->length() < section_contrib_end_pos) {
445 i : LOG(ERROR) << "Invalid DBI header gp_modi_size.";
446 i : return false;
447 : }
448 :
449 : // Run over the section contributions.
450 E : dbi_data += sizeof(uint32); // Skip the signature.
451 E : uint8* section_contrib_end = dbi_data + dbi_header->section_contribution_size;
452 E : while (dbi_data < section_contrib_end) {
453 : pdb::DbiSectionContrib* section_contrib =
454 E : reinterpret_cast<pdb::DbiSectionContrib*>(dbi_data);
455 E : section_contrib->pad1 = 0;
456 E : section_contrib->pad2 = 0;
457 E : dbi_data += sizeof(*section_contrib);
458 E : }
459 :
460 E : return true;
461 E : }
462 :
463 E : bool NormalizeSymbolRecordStream(PdbByteStream* stream) {
464 E : DCHECK(stream != NULL);
465 :
466 E : uint8* data = stream->data();
467 E : uint8* data_end = data + stream->length();
468 :
469 E : while (data < data_end) {
470 : // Get the size of the symbol record and skip past it.
471 E : uint16* size = reinterpret_cast<uint16*>(data);
472 E : data += sizeof(*size);
473 :
474 : // The size of the symbol record, plus its uint16 length, must be a multiple
475 : // of 4. Each symbol record consists of the length followed by a symbol
476 : // type (also a short), so the size needs to be at least of length 2.
477 : // See http://code.google.com/p/sawbuck/wiki/PdbFileFormat for a discussion
478 : // of the format of this stream.
479 E : DCHECK_LE(2u, *size);
480 E : DCHECK_EQ(0u, ((*size + sizeof(*size)) % 4));
481 :
482 : // Up to the last 3 bytes are padding, as the record gets rounded up to
483 : // a multiple of 4 in size.
484 : static const size_t kMaxPadding = 3;
485 E : uint8* end = data + *size;
486 E : uint8* tail = end - kMaxPadding;
487 :
488 : // Skip past the symbol record.
489 E : data = end;
490 :
491 : // Find the null terminator for the record.
492 E : for (; tail + 1 < end && *tail != 0; ++tail) {
493 : // Intentionally empty.
494 E : }
495 :
496 : // Pad out the rest of the record with nulls (these are usually full of
497 : // junk bytes).
498 E : for (; tail < end; ++tail)
499 E : *tail = 0;
500 E : }
501 :
502 E : return true;
503 E : }
504 :
505 : } // namespace
506 :
507 : ZapTimestamp::ZapTimestamp()
508 : : image_layout_(&block_graph_),
509 E : dos_header_block_(NULL) {
510 : // The timestamp can't just be set to zero as that represents a special
511 : // value in the PE file. We set it to some arbitrary fixed date in the past.
512 : // This is Y2K: Jan 1, 2000, 0:00:00 GMT.
513 E : timestamp_data_ = 946684800;
514 :
515 : // Initialize the age to 1.
516 E : pdb_age_data_ = 1;
517 E : }
518 :
519 E : bool ZapTimestamp::Init(const base::FilePath& pe_path) {
520 E : if (!Init(pe_path, base::FilePath()))
521 E : return false;
522 :
523 E : return true;
524 E : }
525 :
526 : bool ZapTimestamp::Init(const base::FilePath& pe_path,
527 E : const base::FilePath& pdb_path) {
528 E : pe_path_ = pe_path;
529 E : pdb_path_ = pdb_path;
530 :
531 E : if (!ValidatePeAndPdbFiles())
532 E : return false;
533 :
534 E : if (!DecomposePeFile())
535 i : return false;
536 :
537 E : if (!MarkPeFileRanges())
538 i : return false;
539 :
540 E : if (!CalculatePdbGuid())
541 i : return false;
542 :
543 E : if (!LoadAndUpdatePdbFile())
544 i : return false;
545 :
546 E : return true;
547 E : }
548 :
549 E : bool ZapTimestamp::Zap(bool modify_pe, bool modify_pdb) {
550 E : if (modify_pe) {
551 E : if (!WritePeFile())
552 i : return false;
553 E : OutputSummaryStats(pe_path_);
554 : }
555 :
556 E : if (modify_pdb) {
557 E : if (!WritePdbFile())
558 i : return false;
559 E : OutputSummaryStats(pdb_path_);
560 : }
561 :
562 E : return true;
563 E : }
564 :
565 E : bool ZapTimestamp::ValidatePeAndPdbFiles() {
566 E : LOG(INFO) << "Analyzing PE file: " << pe_path_.value();
567 :
568 : if (!file_util::PathExists(pe_path_) ||
569 E : file_util::DirectoryExists(pe_path_)) {
570 E : LOG(ERROR) << "PE file not found: " << pe_path_.value();
571 E : return false;
572 : }
573 :
574 E : if (!pe_file_.Init(pe_path_)) {
575 i : LOG(ERROR) << "Failed to read PE file: " << pe_path_.value();
576 i : return false;
577 : }
578 :
579 E : if (pdb_path_.empty()) {
580 : // Find the matching PDB file.
581 E : if (!pe::FindPdbForModule(pe_path_, &pdb_path_)) {
582 i : LOG(ERROR) << "Error while searching for PDB file.";
583 i : return false;
584 : }
585 E : if (pdb_path_.empty()) {
586 E : LOG(ERROR) << "PDB file not found for PE file: " << pe_path_.value();
587 E : return false;
588 : }
589 E : DCHECK(file_util::PathExists(pdb_path_));
590 E : } else {
591 : if (!file_util::PathExists(pdb_path_) ||
592 E : file_util::DirectoryExists(pdb_path_)) {
593 i : LOG(ERROR) << "PDB file not found: " << pdb_path_.value();
594 : }
595 : }
596 :
597 : // Ensure that the PDB and the PE file are consistent with each other.
598 E : if (!pe::PeAndPdbAreMatched(pe_path_, pdb_path_))
599 i : return false; // This logs verbosely.
600 :
601 E : LOG(INFO) << "Found matching PDB file: " << pdb_path_.value();
602 :
603 E : return true;
604 E : }
605 :
606 E : bool ZapTimestamp::DecomposePeFile() {
607 : // Decompose the image. This is a very high level decomposition only
608 : // chunking out the PE structures and references from/to PE blocks.
609 E : BlockGraph::Block* dos_header_block = NULL;
610 E : if (!MiniDecompose(pe_file_, &image_layout_, &dos_header_block_))
611 i : return false;
612 :
613 E : return true;
614 E : }
615 :
616 E : bool ZapTimestamp::MarkPeFileRanges() {
617 E : DCHECK(dos_header_block_ != NULL);
618 E : LOG(INFO) << "Finding PE fields that need updating.";
619 :
620 E : DosHeader dos_header;
621 E : if (!dos_header.Init(0, dos_header_block_)) {
622 i : LOG(ERROR) << "Failed to cast IMAGE_DOS_HEADER.";
623 i : return false;
624 : }
625 :
626 E : NtHeaders nt_headers;
627 E : if (!dos_header.Dereference(dos_header->e_lfanew, &nt_headers)) {
628 i : LOG(ERROR) << "Failed to dereference IMAGE_NT_HEADERS.";
629 i : return false;
630 : }
631 :
632 : // Mark the export data directory timestamp.
633 : if (!MarkDataDirectoryTimestamps<IMAGE_EXPORT_DIRECTORY>(
634 : pe_file_, nt_headers, IMAGE_DIRECTORY_ENTRY_EXPORT,
635 : "Export Directory",
636 : reinterpret_cast<const uint8*>(×tamp_data_),
637 E : &pe_file_addr_space_)) {
638 : // This logs verbosely on failure.
639 i : return false;
640 : }
641 :
642 : // Mark the resource data directory timestamp.
643 : if (!MarkDataDirectoryTimestamps<IMAGE_RESOURCE_DIRECTORY>(
644 : pe_file_, nt_headers, IMAGE_DIRECTORY_ENTRY_RESOURCE,
645 : "Resource Directory",
646 : reinterpret_cast<const uint8*>(×tamp_data_),
647 E : &pe_file_addr_space_)) {
648 : // This logs verbosely on failure.
649 i : return false;
650 : }
651 :
652 : // Find the debug directory.
653 : const IMAGE_DATA_DIRECTORY& debug_dir_info =
654 E : nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
655 E : if (!nt_headers.HasReference(debug_dir_info.VirtualAddress)) {
656 i : LOG(ERROR) << "PE file contains no debug directory.";
657 i : return false;
658 : }
659 E : ImageDebugDirectory debug_dir;
660 E : if (!nt_headers.Dereference(debug_dir_info.VirtualAddress, &debug_dir)) {
661 i : LOG(ERROR) << "Failed to dereference IMAGE_DEBUG_DIRECTORY.";
662 i : return false;
663 : }
664 :
665 E : RelativeAddress rel_addr;
666 :
667 : // Within that, find the codeview debug entry. We also update every other
668 : // debug timestamp.
669 E : CvInfoPdb cv_info_pdb;
670 E : for (size_t i = 0; i < debug_dir.ElementCount(); ++i) {
671 : rel_addr = debug_dir.block()->addr() +
672 E : debug_dir.OffsetOf(debug_dir[i].TimeDateStamp);
673 E : std::string name = base::StringPrintf("Debug Directory %d Timestamp", i);
674 : if (!MarkData(pe_file_, rel_addr, sizeof(timestamp_data_),
675 : reinterpret_cast<const uint8*>(×tamp_data_), name,
676 E : &pe_file_addr_space_)) {
677 i : LOG(ERROR) << "Failed to mark TimeDateStamp of debug directory " << i
678 : << ".";
679 i : return false;
680 : }
681 :
682 E : if (debug_dir[i].Type == IMAGE_DEBUG_TYPE_CODEVIEW) {
683 E : if (cv_info_pdb.block() != NULL) {
684 i : LOG(ERROR) << "Found multiple CodeView debug directories.";
685 i : return false;
686 : }
687 E : if (!debug_dir.Dereference(debug_dir[i].PointerToRawData, &cv_info_pdb)) {
688 i : LOG(ERROR) << "Failed to dereference CodeView debug directory.";
689 i : return false;
690 : }
691 : }
692 E : }
693 :
694 : // We should have found a code view debug directory pointing to the PDB file.
695 E : if (cv_info_pdb.block() == NULL) {
696 i : LOG(ERROR) << "Failed to find CodeView debug directory.";
697 i : return false;
698 : }
699 :
700 : // Get the file offset of the PDB age and mark it.
701 : rel_addr = cv_info_pdb.block()->addr() +
702 E : cv_info_pdb.OffsetOf(cv_info_pdb->pdb_age);
703 : if (!MarkData(pe_file_, rel_addr, sizeof(pdb_age_data_),
704 : reinterpret_cast<const uint8*>(&pdb_age_data_),
705 E : "PDB Age", &pe_file_addr_space_)) {
706 i : LOG(ERROR) << "Failed to mark PDB age.";
707 i : return false;
708 : }
709 :
710 : // Get the file offset of the PDB guid and mark it.
711 : rel_addr = cv_info_pdb.block()->addr() +
712 E : cv_info_pdb.OffsetOf(cv_info_pdb->signature);
713 : if (!MarkData(pe_file_, rel_addr, sizeof(pdb_guid_data_),
714 : reinterpret_cast<const uint8*>(&pdb_guid_data_),
715 E : "PDB GUID", &pe_file_addr_space_)) {
716 i : LOG(ERROR) << "Failed to mark PDB GUID.";
717 i : return false;
718 : }
719 :
720 : // Get the file offset of the PE checksum and mark it.
721 : rel_addr = nt_headers.block()->addr() +
722 E : nt_headers.OffsetOf(nt_headers->OptionalHeader.CheckSum);
723 : if (!MarkData(pe_file_, rel_addr, sizeof(DWORD), NULL,
724 E : "PE Checksum", &pe_file_addr_space_)) {
725 i : LOG(ERROR) << "Failed to mark PE checksum.";
726 i : return false;
727 : }
728 :
729 : // Get the file offset of the PE timestamp and mark it.
730 : rel_addr = nt_headers.block()->addr() +
731 E : nt_headers.OffsetOf(nt_headers->FileHeader.TimeDateStamp);
732 : if (!MarkData(pe_file_, rel_addr, sizeof(timestamp_data_),
733 : reinterpret_cast<uint8*>(×tamp_data_), "PE Timestamp",
734 E : &pe_file_addr_space_)) {
735 i : LOG(ERROR) << "Failed to mark PE timestamp.";
736 i : return false;
737 : }
738 :
739 E : return true;
740 E : }
741 :
742 E : bool ZapTimestamp::CalculatePdbGuid() {
743 E : LOG(INFO) << "Calculating PDB GUID from PE file contents.";
744 :
745 E : file_util::ScopedFILE pe_file(file_util::OpenFile(pe_path_, "rb"));
746 E : if (pe_file.get() == NULL) {
747 i : LOG(ERROR) << "Failed to open PE file for reading: " << pe_path_.value();
748 i : return false;
749 : }
750 :
751 : // Get the length of the entire file.
752 E : if (::fseek(pe_file.get(), 0, SEEK_END) != 0) {
753 i : LOG(ERROR) << "Failed to fseek to end of file.";
754 i : return false;
755 : }
756 E : FileOffsetAddress end(::ftell(pe_file.get()));
757 :
758 : // Seek back to the beginning.
759 E : if (::fseek(pe_file.get(), 0, SEEK_SET) != 0) {
760 i : LOG(ERROR) << "Failed to fseek to beginning of file.";
761 i : return false;
762 : }
763 :
764 : // Initialize the MD5 structure.
765 E : base::MD5Context md5_context = { 0 };
766 E : base::MD5Init(&md5_context);
767 :
768 : // We seek through the bits of the file that will be changed, and skip those.
769 : // The rest of the file (the static parts) are fed through an MD5 hash and
770 : // used to generated a unique and stable GUID.
771 E : FileOffsetAddress cur(0);
772 E : PatchAddressSpace::const_iterator range_it = pe_file_addr_space_.begin();
773 E : for (; range_it != pe_file_addr_space_.end(); ++range_it) {
774 : // Consume any data before this range.
775 E : size_t bytes_to_hash = 0;
776 E : if (cur < range_it->first.start()) {
777 E : bytes_to_hash = range_it->first.start() - cur;
778 E : if (!Md5Consume(bytes_to_hash, pe_file.get(), &md5_context))
779 i : return false; // This logs verbosely for us.
780 : }
781 :
782 E : if (::fseek(pe_file.get(), range_it->first.size(), SEEK_CUR)) {
783 i : LOG(ERROR) << "Failed to fseek past marked range.";
784 : }
785 :
786 E : cur = range_it->first.end();
787 E : }
788 :
789 : // Consume any left-over data.
790 E : if (cur < end) {
791 E : if (!Md5Consume(end - cur, pe_file.get(), &md5_context))
792 i : return false; // This logs verbosely for us.
793 : }
794 :
795 E : DCHECK_EQ(end.value(), static_cast<uint32>(::ftell(pe_file.get())));
796 :
797 : COMPILE_ASSERT(sizeof(base::MD5Digest) == sizeof(pdb_guid_data_),
798 : md5_digest_and_guid_size_mismatch);
799 : base::MD5Final(reinterpret_cast<base::MD5Digest*>(&pdb_guid_data_),
800 E : &md5_context);
801 E : LOG(INFO) << "Final GUID is " << base::MD5DigestToBase16(
802 : *reinterpret_cast<base::MD5Digest*>(&pdb_guid_data_)) << ".";
803 :
804 E : return true;
805 E : }
806 :
807 E : bool ZapTimestamp::LoadAndUpdatePdbFile() {
808 E : DCHECK(pdb_file_.get() == NULL);
809 :
810 E : pdb_file_.reset(new PdbFile());
811 E : PdbReader pdb_reader;
812 E : if (!pdb_reader.Read(pdb_path_, pdb_file_.get())) {
813 i : LOG(ERROR) << "Failed to read PDB file: " << pdb_path_.value();
814 i : return false;
815 : }
816 :
817 : // We turf the old directory stream as a fresh PDB does not have one. It's
818 : // also meaningless after we rewrite a PDB as the old blocks it refers to
819 : // will no longer exist.
820 E : pdb_file_->ReplaceStream(pdb::kPdbOldDirectoryStream, NULL);
821 :
822 : scoped_refptr<PdbStream> header_reader =
823 E : GetWritablePdbStream(pdb::kPdbHeaderInfoStream, pdb_file_.get());
824 E : if (header_reader.get() == NULL) {
825 i : LOG(ERROR) << "No header info stream in PDB file: " << pdb_path_.value();
826 i : return false;
827 : }
828 :
829 : scoped_refptr<WritablePdbStream> header_writer =
830 E : header_reader->GetWritablePdbStream();
831 E : DCHECK(header_writer.get() != NULL);
832 :
833 : // Update the timestamp, the age and the signature.
834 E : LOG(INFO) << "Updating PDB header.";
835 E : header_writer->set_pos(offsetof(pdb::PdbInfoHeader70, timestamp));
836 E : header_writer->Write(static_cast<uint32>(timestamp_data_));
837 E : header_writer->Write(static_cast<uint32>(pdb_age_data_));
838 E : header_writer->Write(pdb_guid_data_);
839 :
840 : // Normalize the DBI stream in place.
841 E : scoped_refptr<PdbByteStream> dbi_stream(new PdbByteStream());
842 E : CHECK(dbi_stream->Init(pdb_file_->GetStream(pdb::kDbiStream)));
843 E : pdb_file_->ReplaceStream(pdb::kDbiStream, dbi_stream);
844 E : if (!NormalizeDbiStream(pdb_age_data_, dbi_stream)) {
845 i : LOG(ERROR) << "Failed to normalize DBI stream.";
846 i : return false;
847 : }
848 :
849 E : uint8* dbi_data = dbi_stream->data();
850 E : pdb::DbiHeader* dbi_header = reinterpret_cast<pdb::DbiHeader*>(dbi_data);
851 :
852 : // Normalize the symbol record stream in place.
853 E : scoped_refptr<PdbByteStream> symrec_stream(new PdbByteStream());
854 : CHECK(symrec_stream->Init(pdb_file_->GetStream(
855 E : dbi_header->symbol_record_stream)));
856 E : pdb_file_->ReplaceStream(dbi_header->symbol_record_stream, symrec_stream);
857 E : if (!NormalizeSymbolRecordStream(symrec_stream)) {
858 i : LOG(ERROR) << "Failed to normalize symbol record stream.";
859 i : return false;
860 : }
861 :
862 : // Normalize the public symbol info stream. There's a DWORD of padding at
863 : // offset 24 that we want to zero.
864 : scoped_refptr<PdbStream> pubsym_reader = GetWritablePdbStream(
865 E : dbi_header->public_symbol_info_stream, pdb_file_.get());
866 : scoped_refptr<WritablePdbStream> pubsym_writer =
867 E : pubsym_reader->GetWritablePdbStream();
868 E : DCHECK(pubsym_writer.get() != NULL);
869 E : pubsym_writer->set_pos(24);
870 E : pubsym_writer->Write(static_cast<uint32>(0));
871 :
872 E : return true;
873 E : }
874 :
875 E : bool ZapTimestamp::WritePeFile() {
876 E : if (!UpdateFileInPlace(pe_path_, pe_file_addr_space_))
877 i : return false;
878 :
879 E : LOG(INFO) << "Updating checksum for PE file: " << pe_path_.value();
880 E : if (!pe::PEFileWriter::UpdateFileChecksum(pe_path_)) {
881 i : LOG(ERROR) << "Failed to update checksum for PE file: " << pe_path_.value();
882 i : return false;
883 : }
884 :
885 E : return true;
886 E : }
887 :
888 E : bool ZapTimestamp::WritePdbFile() {
889 : // We actually completely rewrite the PDB file to a temporary location, and
890 : // then move it over top of the existing one. This is because pdb_file_
891 : // actually has an open file handle to the original PDB.
892 :
893 : // We create a temporary directory alongside the file to be modified so as
894 : // not to cross volume boundaries.
895 E : base::FilePath pdb_dir = pdb_path_.DirName();
896 E : base::ScopedTempDir temp_dir;
897 E : if (!temp_dir.CreateUniqueTempDirUnderPath(pdb_dir)) {
898 i : LOG(ERROR) << "Failed to create temporary directory in \""
899 : << pdb_dir.value() << "\".";
900 i : return false;
901 : }
902 :
903 : // Generate the path to the rewritten PDB.
904 E : base::FilePath temp_path = temp_dir.path().Append(pdb_path_.BaseName());
905 :
906 E : PdbWriter pdb_writer;
907 E : LOG(INFO) << "Creating temporary PDB file: " << temp_path.value();
908 E : if (!pdb_writer.Write(temp_path, *pdb_file_.get())) {
909 i : LOG(ERROR) << "Failed to write new PDB: " << temp_path.value();
910 i : return false;
911 : }
912 :
913 : // Free up the PDB file. This will close the open file handle to the original
914 : // PDB file.
915 E : pdb_file_.reset(NULL);
916 :
917 : // Copy over top of the original file.
918 E : LOG(INFO) << "Temporary PDB file replacing old PDB: "
919 : << pdb_path_.value();
920 E : if (!file_util::ReplaceFileW(temp_path, pdb_path_)) {
921 i : LOG(ERROR) << "Unable to replace PDB file.";
922 i : return false;
923 : }
924 :
925 E : return true;
926 E : }
927 :
928 : bool ZapTimestampApp::ParseCommandLine(const CommandLine* command_line) {
929 : DCHECK(command_line != NULL);
930 :
931 : CommandLine::StringVector args = command_line->GetArgs();
932 : if (args.empty()) {
933 : PrintUsage(out(), command_line->GetProgram(),
934 : "You must provide at least one PE file.");
935 : return false;
936 : }
937 :
938 : for (size_t i = 0; i < args.size(); ++i) {
939 : base::FilePath path(args[i]);
940 : input_modules_.push_back(path);
941 : }
942 :
943 : return true;
944 : }
945 :
946 : int ZapTimestampApp::Run() {
947 : for (size_t i = 0; i < input_modules_.size(); ++i) {
948 : ZapTimestamp zap;
949 : if (!zap.Init(input_modules_[i]) || !zap.Zap(true, true))
950 : return 1;
951 : }
952 :
953 : return 0;
954 : }
955 :
956 : } // namespace zap_timestamp
|