1#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Makes sure that all EXE and DLL files in the provided directory were built
7correctly.
8
9In essense it runs a subset of BinScope tests ensuring that binaries have
10/NXCOMPAT, /DYNAMICBASE and /SAFESEH.
11"""
12
13import os
14import optparse
15import sys
16
17# Find /third_party/pefile based on current directory and script path.
18sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..',
19                             'third_party', 'pefile'))
20import pefile
21
22PE_FILE_EXTENSIONS = ['.exe', '.dll']
23DYNAMICBASE_FLAG = 0x0040
24NXCOMPAT_FLAG = 0x0100
25NO_SEH_FLAG = 0x0400
26MACHINE_TYPE_AMD64 = 0x8664
27
28# Please do not add your file here without confirming that it indeed doesn't
29# require /NXCOMPAT and /DYNAMICBASE.  Contact cpu@chromium.org or your local
30# Windows guru for advice.
31EXCLUDED_FILES = ['chrome_frame_mini_installer.exe',
32                  'mini_installer.exe',
33                  'wow_helper.exe',
34                  'xinput1_3.dll' # Microsoft DirectX redistributable.
35                  ]
36
37def IsPEFile(path):
38  return (os.path.isfile(path) and
39          os.path.splitext(path)[1].lower() in PE_FILE_EXTENSIONS and
40          os.path.basename(path) not in EXCLUDED_FILES)
41
42def main(options, args):
43  directory = args[0]
44  pe_total = 0
45  pe_passed = 0
46
47  for file in os.listdir(directory):
48    path = os.path.abspath(os.path.join(directory, file))
49    if not IsPEFile(path):
50      continue
51    pe = pefile.PE(path, fast_load=True)
52    pe.parse_data_directories(directories=[
53        pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']])
54    pe_total = pe_total + 1
55    success = True
56
57    # Check for /DYNAMICBASE.
58    if pe.OPTIONAL_HEADER.DllCharacteristics & DYNAMICBASE_FLAG:
59      if options.verbose:
60        print "Checking %s for /DYNAMICBASE... PASS" % path
61    else:
62      success = False
63      print "Checking %s for /DYNAMICBASE... FAIL" % path
64
65    # Check for /NXCOMPAT.
66    if pe.OPTIONAL_HEADER.DllCharacteristics & NXCOMPAT_FLAG:
67      if options.verbose:
68        print "Checking %s for /NXCOMPAT... PASS" % path
69    else:
70      success = False
71      print "Checking %s for /NXCOMPAT... FAIL" % path
72
73    # Check for /SAFESEH. Binaries should meet one of the following
74    # criteria:
75    #   1) Have no SEH table as indicated by the DLL characteristics
76    #   2) Have a LOAD_CONFIG section containing a valid SEH table
77    #   3) Be a 64-bit binary, in which case /SAFESEH isn't required
78    #
79    # Refer to the following MSDN article for more information:
80    # http://msdn.microsoft.com/en-us/library/9a89h429.aspx
81    if (pe.OPTIONAL_HEADER.DllCharacteristics & NO_SEH_FLAG or
82        (hasattr(pe, "DIRECTORY_ENTRY_LOAD_CONFIG") and
83         pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerCount > 0 and
84         pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerTable != 0) or
85        pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64):
86      if options.verbose:
87        print "Checking %s for /SAFESEH... PASS" % path
88    else:
89      success = False
90      print "Checking %s for /SAFESEH... FAIL" % path
91
92    # ASLR is weakened on Windows 64-bit when the ImageBase is below 4GB
93    # (because the loader will never be rebase the image above 4GB).
94    if pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64:
95      if pe.OPTIONAL_HEADER.ImageBase <= 0xFFFFFFFF:
96        print("Checking %s ImageBase (0x%X < 4GB)... FAIL" %
97              (path, pe.OPTIONAL_HEADER.ImageBase))
98        success = False
99      elif options.verbose:
100        print("Checking %s ImageBase (0x%X > 4GB)... PASS" %
101              (path, pe.OPTIONAL_HEADER.ImageBase))
102
103    # Update tally.
104    if success:
105      pe_passed = pe_passed + 1
106
107  print "Result: %d files found, %d files passed" % (pe_total, pe_passed)
108  if pe_passed != pe_total:
109    sys.exit(1)
110
111if __name__ == '__main__':
112  usage = "Usage: %prog [options] DIRECTORY"
113  option_parser = optparse.OptionParser(usage=usage)
114  option_parser.add_option("-v", "--verbose", action="store_true",
115                           default=False, help="Print debug logging")
116  options, args = option_parser.parse_args()
117  if not args:
118    option_parser.print_help()
119    sys.exit(0)
120  main(options, args)
121