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/scoped_temp_dir.h"
41 : #include "base/stringprintf.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 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 E : bool UpdateFileInPlace(const FilePath& path, const PatchAddressSpace& updates) {
307 E : LOG(INFO) << "Patching file: " << path.value();
308 :
309 E : file_util::ScopedFILE file(file_util::OpenFile(path, "rb+"));
310 E : if (file.get() == NULL) {
311 i : LOG(ERROR) << "Unable to open file for updating: " << path.value();
312 i : return false;
313 : }
314 :
315 E : PatchAddressSpace::const_iterator it = updates.begin();
316 E : for (; it != updates.end(); ++it) {
317 : // No data? Then nothing to update. This happens for the PE checksum, which
318 : // has a NULL data pointer. We update it later on in another pass.
319 E : if (it->second.data == NULL)
320 E : continue;
321 :
322 E : LOG(INFO) << " Patching " << it->second.name << ", " << it->first.size()
323 : << " bytes at " << it->first.start();
324 :
325 : // Seek to the position to be updated.
326 E : if (::fseek(file.get(), it->first.start().value(), SEEK_SET) != 0) {
327 i : LOG(ERROR) << "Failed to seek to " << it->first.start() << " of file: "
328 : << path.value();
329 i : return false;
330 : }
331 :
332 : // Write the updated data.
333 : size_t bytes_written = ::fwrite(it->second.data, 1, it->first.size(),
334 E : file.get());
335 E : if (bytes_written != it->first.size()) {
336 i : LOG(ERROR) << "Failed to write " << it->first.size() << " bytes to "
337 : << "position " << it->first.start() << " of file: "
338 : << path.value();
339 : }
340 E : }
341 :
342 E : LOG(INFO) << "Finished patching file: " << path.value();
343 E : file.reset();
344 :
345 E : return true;
346 E : }
347 :
348 : // Ensures that the stream with the given ID is writable, returning a scoped
349 : // pointer to it.
350 : scoped_refptr<PdbStream> GetWritablePdbStream(size_t index,
351 E : PdbFile* pdb_file) {
352 E : DCHECK(pdb_file != NULL);
353 E : DCHECK_GT(pdb_file->StreamCount(), index);
354 :
355 E : scoped_refptr<PdbStream> reader = pdb_file->GetStream(index);
356 :
357 : // Try and get the writer. If it's not available, then replace the stream
358 : // with a byte stream, which is in-place writable.
359 E : scoped_refptr<WritablePdbStream> writer = reader->GetWritablePdbStream();
360 E : if (writer.get() == NULL) {
361 E : scoped_refptr<PdbByteStream> byte_stream(new PdbByteStream());
362 E : byte_stream->Init(reader);
363 E : pdb_file->ReplaceStream(index, byte_stream);
364 E : reader = byte_stream;
365 E : }
366 :
367 E : return reader;
368 E : }
369 :
370 E : void OutputSummaryStats(FilePath& path) {
371 E : file_util::ScopedFILE file(file_util::OpenFile(path, "rb"));
372 E : if (file.get() == NULL) {
373 i : LOG(ERROR) << "Unable to open file for reading: " << path.value();
374 i : return;
375 : }
376 E : ::fseek(file.get(), 0, SEEK_END);
377 E : size_t file_size = ::ftell(file.get());
378 E : ::fseek(file.get(), 0, SEEK_SET);
379 :
380 : base::MD5Context md5_context;
381 E : base::MD5Init(&md5_context);
382 E : if (!Md5Consume(file_size, file.get(), &md5_context))
383 i : return;
384 :
385 : base::MD5Digest md5_digest;
386 E : base::MD5Final(&md5_digest, &md5_context);
387 E : std::string md5_string = base::MD5DigestToBase16(md5_digest);
388 :
389 E : LOG(INFO) << "Path: " << path.value();
390 E : LOG(INFO) << " Size : " << file_size;
391 E : LOG(INFO) << " Digest: " << md5_string;
392 E : }
393 :
394 : bool NormalizeDbiStream(DWORD pdb_age_data,
395 E : PdbByteStream* dbi_stream) {
396 E : DCHECK(dbi_stream != NULL);
397 :
398 E : LOG(INFO) << "Updating PDB DBI stream.";
399 :
400 E : uint8* dbi_data = dbi_stream->data();
401 E : if (dbi_stream->length() < sizeof(pdb::DbiHeader)) {
402 i : LOG(ERROR) << "DBI stream too short.";
403 i : return false;
404 : }
405 E : pdb::DbiHeader* dbi_header = reinterpret_cast<pdb::DbiHeader*>(dbi_data);
406 :
407 : // Update the age in the DbiHeader as well. This needs to match pdb_age
408 : // in the PDB header.
409 E : dbi_header->age = pdb_age_data;
410 E : dbi_data += sizeof(*dbi_header);
411 :
412 : // Ensure that the module information is addressable.
413 E : if (dbi_stream->length() < dbi_header->gp_modi_size) {
414 i : LOG(ERROR) << "Invalid DBI header gp_modi_size.";
415 i : return false;
416 : }
417 :
418 : // Run over the module information.
419 : // TODO(chrisha): Use BufferWriter to do this. We need to update it to handle
420 : // type casts and bounds checking.
421 E : uint8* module_info_end = dbi_data + dbi_header->gp_modi_size;
422 E : while (dbi_data < module_info_end) {
423 : pdb::DbiModuleInfoBase* module_info =
424 E : reinterpret_cast<pdb::DbiModuleInfoBase*>(dbi_data);
425 E : module_info->offsets = 0;
426 E : dbi_data += sizeof(*module_info);
427 :
428 : // Skip two NULL terminated strings after the module info.
429 E : while (*dbi_data != 0) ++dbi_data;
430 E : ++dbi_data;
431 E : while (*dbi_data != 0) ++dbi_data;
432 E : ++dbi_data;
433 :
434 : // Skip until we're at a multiple of 4 position.
435 E : size_t offset = dbi_data - dbi_stream->data();
436 E : offset = ((offset + 3) / 4) * 4;
437 E : dbi_data = dbi_stream->data() + offset;
438 E : }
439 :
440 : // Ensure that the section contributions are addressable.
441 : size_t section_contrib_end_pos = dbi_header->gp_modi_size + sizeof(uint32) +
442 E : dbi_header->section_contribution_size;
443 E : if (dbi_stream->length() < section_contrib_end_pos) {
444 i : LOG(ERROR) << "Invalid DBI header gp_modi_size.";
445 i : return false;
446 : }
447 :
448 : // Run over the section contributions.
449 E : dbi_data += sizeof(uint32); // Skip the signature.
450 E : uint8* section_contrib_end = dbi_data + dbi_header->section_contribution_size;
451 E : while (dbi_data < section_contrib_end) {
452 : pdb::DbiSectionContrib* section_contrib =
453 E : reinterpret_cast<pdb::DbiSectionContrib*>(dbi_data);
454 E : section_contrib->pad1 = 0;
455 E : section_contrib->pad2 = 0;
456 E : dbi_data += sizeof(*section_contrib);
457 E : }
458 :
459 E : return true;
460 E : }
461 :
462 E : bool NormalizeSymbolRecordStream(PdbByteStream* stream) {
463 E : DCHECK(stream != NULL);
464 :
465 E : uint8* data = stream->data();
466 E : uint8* data_end = data + stream->length();
467 :
468 E : while (data < data_end) {
469 : // Get the size of the symbol record and skip past it.
470 E : uint16* size = reinterpret_cast<uint16*>(data);
471 E : data += sizeof(*size);
472 :
473 : // The size of the symbol record, plus its uint16 length, must be a multiple
474 : // of 4. Each symbol record consists of the length followed by a symbol
475 : // type (also a short), so the size needs to be at least of length 2.
476 : // See http://code.google.com/p/sawbuck/wiki/PdbFileFormat for a discussion
477 : // of the format of this stream.
478 E : DCHECK_LE(2u, *size);
479 E : DCHECK_EQ(0u, ((*size + sizeof(*size)) % 4));
480 :
481 : // Up to the last 3 bytes are padding, as the record gets rounded up to
482 : // a multiple of 4 in size.
483 : static const size_t kMaxPadding = 3;
484 E : uint8* end = data + *size;
485 E : uint8* tail = end - kMaxPadding;
486 :
487 : // Skip past the symbol record.
488 E : data = end;
489 :
490 : // Find the null terminator for the record.
491 E : for (; tail + 1 < end && *tail != 0; ++tail) {
492 : // Intentionally empty.
493 E : }
494 :
495 : // Pad out the rest of the record with nulls (these are usually full of
496 : // junk bytes).
497 E : for (; tail < end; ++tail)
498 E : *tail = 0;
499 E : }
500 :
501 E : return true;
502 E : }
503 :
504 : } // namespace
505 :
506 : ZapTimestamp::ZapTimestamp()
507 : : image_layout_(&block_graph_),
508 E : dos_header_block_(NULL) {
509 : // The timestamp can't just be set to zero as that represents a special
510 : // value in the PE file. We set it to some arbitrary fixed date in the past.
511 : // This is Y2K: Jan 1, 2000, 0:00:00 GMT.
512 E : timestamp_data_ = 946684800;
513 :
514 : // Initialize the age to 1.
515 E : pdb_age_data_ = 1;
516 E : }
517 :
518 E : bool ZapTimestamp::Init(const FilePath& pe_path) {
519 E : if (!Init(pe_path, FilePath()))
520 E : return false;
521 :
522 E : return true;
523 E : }
524 :
525 E : bool ZapTimestamp::Init(const FilePath& pe_path, const FilePath& pdb_path) {
526 E : pe_path_ = pe_path;
527 E : pdb_path_ = pdb_path;
528 :
529 E : if (!ValidatePeAndPdbFiles())
530 E : return false;
531 :
532 E : if (!DecomposePeFile())
533 i : return false;
534 :
535 E : if (!MarkPeFileRanges())
536 i : return false;
537 :
538 E : if (!CalculatePdbGuid())
539 i : return false;
540 :
541 E : if (!LoadAndUpdatePdbFile())
542 i : return false;
543 :
544 E : return true;
545 E : }
546 :
547 E : bool ZapTimestamp::Zap(bool modify_pe, bool modify_pdb) {
548 E : if (modify_pe) {
549 E : if (!WritePeFile())
550 i : return false;
551 E : OutputSummaryStats(pe_path_);
552 : }
553 :
554 E : if (modify_pdb) {
555 E : if (!WritePdbFile())
556 i : return false;
557 E : OutputSummaryStats(pdb_path_);
558 : }
559 :
560 E : return true;
561 E : }
562 :
563 E : bool ZapTimestamp::ValidatePeAndPdbFiles() {
564 E : LOG(INFO) << "Analyzing PE file: " << pe_path_.value();
565 :
566 : if (!file_util::PathExists(pe_path_) ||
567 E : file_util::DirectoryExists(pe_path_)) {
568 E : LOG(ERROR) << "PE file not found: " << pe_path_.value();
569 E : return false;
570 : }
571 :
572 E : if (!pe_file_.Init(pe_path_)) {
573 i : LOG(ERROR) << "Failed to read PE file: " << pe_path_.value();
574 i : return false;
575 : }
576 :
577 E : if (pdb_path_.empty()) {
578 : // Find the matching PDB file.
579 E : if (!pe::FindPdbForModule(pe_path_, &pdb_path_)) {
580 i : LOG(ERROR) << "Error while searching for PDB file.";
581 i : return false;
582 : }
583 E : if (pdb_path_.empty()) {
584 E : LOG(ERROR) << "PDB file not found for PE file: " << pe_path_.value();
585 E : return false;
586 : }
587 E : DCHECK(file_util::PathExists(pdb_path_));
588 E : } else {
589 : if (!file_util::PathExists(pdb_path_) ||
590 E : file_util::DirectoryExists(pdb_path_)) {
591 i : LOG(ERROR) << "PDB file not found: " << pdb_path_.value();
592 : }
593 : }
594 :
595 : // Ensure that the PDB and the PE file are consistent with each other.
596 E : if (!pe::PeAndPdbAreMatched(pe_path_, pdb_path_))
597 i : return false; // This logs verbosely.
598 :
599 E : LOG(INFO) << "Found matching PDB file: " << pdb_path_.value();
600 :
601 E : return true;
602 E : }
603 :
604 E : bool ZapTimestamp::DecomposePeFile() {
605 : // Decompose the image. This is a very high level decomposition only
606 : // chunking out the PE structures and references from/to PE blocks.
607 E : BlockGraph::Block* dos_header_block = NULL;
608 E : if (!MiniDecompose(pe_file_, &image_layout_, &dos_header_block_))
609 i : return false;
610 :
611 E : return true;
612 E : }
613 :
614 E : bool ZapTimestamp::MarkPeFileRanges() {
615 E : DCHECK(dos_header_block_ != NULL);
616 E : LOG(INFO) << "Finding PE fields that need updating.";
617 :
618 E : DosHeader dos_header;
619 E : if (!dos_header.Init(0, dos_header_block_)) {
620 i : LOG(ERROR) << "Failed to cast IMAGE_DOS_HEADER.";
621 i : return false;
622 : }
623 :
624 E : NtHeaders nt_headers;
625 E : if (!dos_header.Dereference(dos_header->e_lfanew, &nt_headers)) {
626 i : LOG(ERROR) << "Failed to dereference IMAGE_NT_HEADERS.";
627 i : return false;
628 : }
629 :
630 : // Mark the export data directory timestamp.
631 : if (!MarkDataDirectoryTimestamps<IMAGE_EXPORT_DIRECTORY>(
632 : pe_file_, nt_headers, IMAGE_DIRECTORY_ENTRY_EXPORT,
633 : "Export Directory",
634 : reinterpret_cast<const uint8*>(×tamp_data_),
635 E : &pe_file_addr_space_)) {
636 : // This logs verbosely on failure.
637 i : return false;
638 : }
639 :
640 : // Mark the resource data directory timestamp.
641 : if (!MarkDataDirectoryTimestamps<IMAGE_RESOURCE_DIRECTORY>(
642 : pe_file_, nt_headers, IMAGE_DIRECTORY_ENTRY_RESOURCE,
643 : "Resource Directory",
644 : reinterpret_cast<const uint8*>(×tamp_data_),
645 E : &pe_file_addr_space_)) {
646 : // This logs verbosely on failure.
647 i : return false;
648 : }
649 :
650 : // Find the debug directory.
651 : const IMAGE_DATA_DIRECTORY& debug_dir_info =
652 E : nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
653 E : if (!nt_headers.HasReference(debug_dir_info.VirtualAddress)) {
654 i : LOG(ERROR) << "PE file contains no debug directory.";
655 i : return false;
656 : }
657 E : ImageDebugDirectory debug_dir;
658 E : if (!nt_headers.Dereference(debug_dir_info.VirtualAddress, &debug_dir)) {
659 i : LOG(ERROR) << "Failed to dereference IMAGE_DEBUG_DIRECTORY.";
660 i : return false;
661 : }
662 :
663 E : RelativeAddress rel_addr;
664 :
665 : // Within that, find the codeview debug entry. We also update every other
666 : // debug timestamp.
667 E : CvInfoPdb cv_info_pdb;
668 E : for (size_t i = 0; i < debug_dir.ElementCount(); ++i) {
669 : rel_addr = debug_dir.block()->addr() +
670 E : debug_dir.OffsetOf(debug_dir[i].TimeDateStamp);
671 E : std::string name = base::StringPrintf("Debug Directory %d Timestamp", i);
672 : if (!MarkData(pe_file_, rel_addr, sizeof(timestamp_data_),
673 : reinterpret_cast<const uint8*>(×tamp_data_), name,
674 E : &pe_file_addr_space_)) {
675 i : LOG(ERROR) << "Failed to mark TimeDateStamp of debug directory " << i
676 : << ".";
677 i : return false;
678 : }
679 :
680 E : if (debug_dir[i].Type == IMAGE_DEBUG_TYPE_CODEVIEW) {
681 E : if (cv_info_pdb.block() != NULL) {
682 i : LOG(ERROR) << "Found multiple CodeView debug directories.";
683 i : return false;
684 : }
685 E : if (!debug_dir.Dereference(debug_dir[i].PointerToRawData, &cv_info_pdb)) {
686 i : LOG(ERROR) << "Failed to dereference CodeView debug directory.";
687 i : return false;
688 : }
689 : }
690 E : }
691 :
692 : // We should have found a code view debug directory pointing to the PDB file.
693 E : if (cv_info_pdb.block() == NULL) {
694 i : LOG(ERROR) << "Failed to find CodeView debug directory.";
695 i : return false;
696 : }
697 :
698 : // Get the file offset of the PDB age and mark it.
699 : rel_addr = cv_info_pdb.block()->addr() +
700 E : cv_info_pdb.OffsetOf(cv_info_pdb->pdb_age);
701 : if (!MarkData(pe_file_, rel_addr, sizeof(pdb_age_data_),
702 : reinterpret_cast<const uint8*>(&pdb_age_data_),
703 E : "PDB Age", &pe_file_addr_space_)) {
704 i : LOG(ERROR) << "Failed to mark PDB age.";
705 i : return false;
706 : }
707 :
708 : // Get the file offset of the PDB guid and mark it.
709 : rel_addr = cv_info_pdb.block()->addr() +
710 E : cv_info_pdb.OffsetOf(cv_info_pdb->signature);
711 : if (!MarkData(pe_file_, rel_addr, sizeof(pdb_guid_data_),
712 : reinterpret_cast<const uint8*>(&pdb_guid_data_),
713 E : "PDB GUID", &pe_file_addr_space_)) {
714 i : LOG(ERROR) << "Failed to mark PDB GUID.";
715 i : return false;
716 : }
717 :
718 : // Get the file offset of the PE checksum and mark it.
719 : rel_addr = nt_headers.block()->addr() +
720 E : nt_headers.OffsetOf(nt_headers->OptionalHeader.CheckSum);
721 : if (!MarkData(pe_file_, rel_addr, sizeof(DWORD), NULL,
722 E : "PE Checksum", &pe_file_addr_space_)) {
723 i : LOG(ERROR) << "Failed to mark PE checksum.";
724 i : return false;
725 : }
726 :
727 : // Get the file offset of the PE timestamp and mark it.
728 : rel_addr = nt_headers.block()->addr() +
729 E : nt_headers.OffsetOf(nt_headers->FileHeader.TimeDateStamp);
730 : if (!MarkData(pe_file_, rel_addr, sizeof(timestamp_data_),
731 : reinterpret_cast<uint8*>(×tamp_data_), "PE Timestamp",
732 E : &pe_file_addr_space_)) {
733 i : LOG(ERROR) << "Failed to mark PE timestamp.";
734 i : return false;
735 : }
736 :
737 E : return true;
738 E : }
739 :
740 E : bool ZapTimestamp::CalculatePdbGuid() {
741 E : LOG(INFO) << "Calculating PDB GUID from PE file contents.";
742 :
743 E : file_util::ScopedFILE pe_file(file_util::OpenFile(pe_path_, "rb"));
744 E : if (pe_file.get() == NULL) {
745 i : LOG(ERROR) << "Failed to open PE file for reading: " << pe_path_.value();
746 i : return false;
747 : }
748 :
749 : // Get the length of the entire file.
750 E : if (::fseek(pe_file.get(), 0, SEEK_END) != 0) {
751 i : LOG(ERROR) << "Failed to fseek to end of file.";
752 i : return false;
753 : }
754 E : FileOffsetAddress end(::ftell(pe_file.get()));
755 :
756 : // Seek back to the beginning.
757 E : if (::fseek(pe_file.get(), 0, SEEK_SET) != 0) {
758 i : LOG(ERROR) << "Failed to fseek to beginning of file.";
759 i : return false;
760 : }
761 :
762 : // Initialize the MD5 structure.
763 E : base::MD5Context md5_context = { 0 };
764 E : base::MD5Init(&md5_context);
765 :
766 : // We seek through the bits of the file that will be changed, and skip those.
767 : // The rest of the file (the static parts) are fed through an MD5 hash and
768 : // used to generated a unique and stable GUID.
769 E : FileOffsetAddress cur(0);
770 E : PatchAddressSpace::const_iterator range_it = pe_file_addr_space_.begin();
771 E : for (; range_it != pe_file_addr_space_.end(); ++range_it) {
772 : // Consume any data before this range.
773 E : size_t bytes_to_hash = 0;
774 E : if (cur < range_it->first.start()) {
775 E : bytes_to_hash = range_it->first.start() - cur;
776 E : if (!Md5Consume(bytes_to_hash, pe_file.get(), &md5_context))
777 i : return false; // This logs verbosely for us.
778 : }
779 :
780 E : if (::fseek(pe_file.get(), range_it->first.size(), SEEK_CUR)) {
781 i : LOG(ERROR) << "Failed to fseek past marked range.";
782 : }
783 :
784 E : cur = range_it->first.end();
785 E : }
786 :
787 : // Consume any left-over data.
788 E : if (cur < end) {
789 E : if (!Md5Consume(end - cur, pe_file.get(), &md5_context))
790 i : return false; // This logs verbosely for us.
791 : }
792 :
793 E : DCHECK_EQ(end.value(), static_cast<uint32>(::ftell(pe_file.get())));
794 :
795 : COMPILE_ASSERT(sizeof(base::MD5Digest) == sizeof(pdb_guid_data_),
796 : md5_digest_and_guid_size_mismatch);
797 : base::MD5Final(reinterpret_cast<base::MD5Digest*>(&pdb_guid_data_),
798 E : &md5_context);
799 E : LOG(INFO) << "Final GUID is " << base::MD5DigestToBase16(
800 : *reinterpret_cast<base::MD5Digest*>(&pdb_guid_data_)) << ".";
801 :
802 E : return true;
803 E : }
804 :
805 E : bool ZapTimestamp::LoadAndUpdatePdbFile() {
806 E : DCHECK(pdb_file_.get() == NULL);
807 :
808 E : pdb_file_.reset(new PdbFile());
809 E : PdbReader pdb_reader;
810 E : if (!pdb_reader.Read(pdb_path_, pdb_file_.get())) {
811 i : LOG(ERROR) << "Failed to read PDB file: " << pdb_path_.value();
812 i : return false;
813 : }
814 :
815 : // We turf the old directory stream as a fresh PDB does not have one. It's
816 : // also meaningless after we rewrite a PDB as the old blocks it refers to
817 : // will no longer exist.
818 E : pdb_file_->ReplaceStream(pdb::kPdbOldDirectoryStream, NULL);
819 :
820 : scoped_refptr<PdbStream> header_reader =
821 E : GetWritablePdbStream(pdb::kPdbHeaderInfoStream, pdb_file_.get());
822 E : if (header_reader.get() == NULL) {
823 i : LOG(ERROR) << "No header info stream in PDB file: " << pdb_path_.value();
824 i : return false;
825 : }
826 :
827 : scoped_refptr<WritablePdbStream> header_writer =
828 E : header_reader->GetWritablePdbStream();
829 E : DCHECK(header_writer.get() != NULL);
830 :
831 : // Update the timestamp, the age and the signature.
832 E : LOG(INFO) << "Updating PDB header.";
833 E : header_writer->set_pos(offsetof(pdb::PdbInfoHeader70, timestamp));
834 E : header_writer->Write(static_cast<uint32>(timestamp_data_));
835 E : header_writer->Write(static_cast<uint32>(pdb_age_data_));
836 E : header_writer->Write(pdb_guid_data_);
837 :
838 : // Normalize the DBI stream in place.
839 E : scoped_refptr<PdbByteStream> dbi_stream(new PdbByteStream());
840 E : CHECK(dbi_stream->Init(pdb_file_->GetStream(pdb::kDbiStream)));
841 E : pdb_file_->ReplaceStream(pdb::kDbiStream, dbi_stream);
842 E : if (!NormalizeDbiStream(pdb_age_data_, dbi_stream)) {
843 i : LOG(ERROR) << "Failed to normalize DBI stream.";
844 i : return false;
845 : }
846 :
847 E : uint8* dbi_data = dbi_stream->data();
848 E : pdb::DbiHeader* dbi_header = reinterpret_cast<pdb::DbiHeader*>(dbi_data);
849 :
850 : // Normalize the symbol record stream in place.
851 E : scoped_refptr<PdbByteStream> symrec_stream(new PdbByteStream());
852 : CHECK(symrec_stream->Init(pdb_file_->GetStream(
853 E : dbi_header->symbol_record_stream)));
854 E : pdb_file_->ReplaceStream(dbi_header->symbol_record_stream, symrec_stream);
855 E : if (!NormalizeSymbolRecordStream(symrec_stream)) {
856 i : LOG(ERROR) << "Failed to normalize symbol record stream.";
857 i : return false;
858 : }
859 :
860 : // Normalize the public symbol info stream. There's a DWORD of padding at
861 : // offset 24 that we want to zero.
862 : scoped_refptr<PdbStream> pubsym_reader = GetWritablePdbStream(
863 E : dbi_header->public_symbol_info_stream, pdb_file_.get());
864 : scoped_refptr<WritablePdbStream> pubsym_writer =
865 E : pubsym_reader->GetWritablePdbStream();
866 E : DCHECK(pubsym_writer.get() != NULL);
867 E : pubsym_writer->set_pos(24);
868 E : pubsym_writer->Write(static_cast<uint32>(0));
869 :
870 E : return true;
871 E : }
872 :
873 E : bool ZapTimestamp::WritePeFile() {
874 E : if (!UpdateFileInPlace(pe_path_, pe_file_addr_space_))
875 i : return false;
876 :
877 E : LOG(INFO) << "Updating checksum for PE file: " << pe_path_.value();
878 E : if (!pe::PEFileWriter::UpdateFileChecksum(pe_path_)) {
879 i : LOG(ERROR) << "Failed to update checksum for PE file: " << pe_path_.value();
880 i : return false;
881 : }
882 :
883 E : return true;
884 E : }
885 :
886 E : bool ZapTimestamp::WritePdbFile() {
887 : // We actually completely rewrite the PDB file to a temporary location, and
888 : // then move it over top of the existing one. This is because pdb_file_
889 : // actually has an open file handle to the original PDB.
890 :
891 : // We create a temporary directory alongside the file to be modified so as
892 : // not to cross volume boundaries.
893 E : FilePath pdb_dir = pdb_path_.DirName();
894 E : ScopedTempDir temp_dir;
895 E : if (!temp_dir.CreateUniqueTempDirUnderPath(pdb_dir)) {
896 i : LOG(ERROR) << "Failed to create temporary directory in \""
897 : << pdb_dir.value() << "\".";
898 i : return false;
899 : }
900 :
901 : // Generate the path to the rewritten PDB.
902 E : FilePath temp_path = temp_dir.path().Append(pdb_path_.BaseName());
903 :
904 E : PdbWriter pdb_writer;
905 E : LOG(INFO) << "Creating temporary PDB file: " << temp_path.value();
906 E : if (!pdb_writer.Write(temp_path, *pdb_file_.get())) {
907 i : LOG(ERROR) << "Failed to write new PDB: " << temp_path.value();
908 i : return false;
909 : }
910 :
911 : // Free up the PDB file. This will close the open file handle to the original
912 : // PDB file.
913 E : pdb_file_.reset(NULL);
914 :
915 : // Copy over top of the original file.
916 E : LOG(INFO) << "Temporary PDB file replacing old PDB: "
917 : << pdb_path_.value();
918 E : if (!file_util::ReplaceFileW(temp_path, pdb_path_)) {
919 i : LOG(ERROR) << "Unable to replace PDB file.";
920 i : return false;
921 : }
922 :
923 E : return true;
924 E : }
925 :
926 : bool ZapTimestampApp::ParseCommandLine(const CommandLine* command_line) {
927 : DCHECK(command_line != NULL);
928 :
929 : CommandLine::StringVector args = command_line->GetArgs();
930 : if (args.empty()) {
931 : PrintUsage(out(), command_line->GetProgram(),
932 : "You must provide at least one PE file.");
933 : return false;
934 : }
935 :
936 : for (size_t i = 0; i < args.size(); ++i) {
937 : FilePath path(args[i]);
938 : input_modules_.push_back(path);
939 : }
940 :
941 : return true;
942 : }
943 :
944 : int ZapTimestampApp::Run() {
945 : for (size_t i = 0; i < input_modules_.size(); ++i) {
946 : ZapTimestamp zap;
947 : if (!zap.Init(input_modules_[i]) || !zap.Zap(true, true))
948 : return 1;
949 : }
950 :
951 : return 0;
952 : }
953 :
954 : } // namespace zap_timestamp
|