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 13from __future__ import print_function 14 15import json 16import os 17import optparse 18import sys 19 20# Find /third_party/pefile based on current directory and script path. 21sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 22 'third_party', 'pefile')) 23import pefile 24 25PE_FILE_EXTENSIONS = ['.exe', '.dll'] 26DYNAMICBASE_FLAG = 0x0040 27NXCOMPAT_FLAG = 0x0100 28NO_SEH_FLAG = 0x0400 29MACHINE_TYPE_AMD64 = 0x8664 30 31# Please do not add your file here without confirming that it indeed doesn't 32# require /NXCOMPAT and /DYNAMICBASE. Contact cpu@chromium.org or your local 33# Windows guru for advice. 34EXCLUDED_FILES = [ 35 'crashpad_util_test_process_info_test_child.exe', 36 'mini_installer.exe', 37 'previous_version_mini_installer.exe', 38 ] 39 40def IsPEFile(path): 41 return (os.path.isfile(path) and 42 os.path.splitext(path)[1].lower() in PE_FILE_EXTENSIONS and 43 os.path.basename(path) not in EXCLUDED_FILES) 44 45def main(options, args): 46 directory = args[0] 47 pe_total = 0 48 pe_passed = 0 49 50 failures = [] 51 52 for file in os.listdir(directory): 53 path = os.path.abspath(os.path.join(directory, file)) 54 if not IsPEFile(path): 55 continue 56 pe = pefile.PE(path, fast_load=True) 57 pe.parse_data_directories(directories=[ 58 pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']]) 59 pe_total = pe_total + 1 60 success = True 61 62 # Check for /DYNAMICBASE. 63 if pe.OPTIONAL_HEADER.DllCharacteristics & DYNAMICBASE_FLAG: 64 if options.verbose: 65 print("Checking %s for /DYNAMICBASE... PASS" % path) 66 else: 67 success = False 68 print("Checking %s for /DYNAMICBASE... FAIL" % path) 69 70 # Check for /NXCOMPAT. 71 if pe.OPTIONAL_HEADER.DllCharacteristics & NXCOMPAT_FLAG: 72 if options.verbose: 73 print("Checking %s for /NXCOMPAT... PASS" % path) 74 else: 75 success = False 76 print("Checking %s for /NXCOMPAT... FAIL" % path) 77 78 # Check for /SAFESEH. Binaries should meet one of the following 79 # criteria: 80 # 1) Have no SEH table as indicated by the DLL characteristics 81 # 2) Have a LOAD_CONFIG section containing a valid SEH table 82 # 3) Be a 64-bit binary, in which case /SAFESEH isn't required 83 # 84 # Refer to the following MSDN article for more information: 85 # http://msdn.microsoft.com/en-us/library/9a89h429.aspx 86 if (pe.OPTIONAL_HEADER.DllCharacteristics & NO_SEH_FLAG or 87 (hasattr(pe, "DIRECTORY_ENTRY_LOAD_CONFIG") and 88 pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerCount > 0 and 89 pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerTable != 0) or 90 pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64): 91 if options.verbose: 92 print("Checking %s for /SAFESEH... PASS" % path) 93 else: 94 success = False 95 print("Checking %s for /SAFESEH... FAIL" % path) 96 97 # ASLR is weakened on Windows 64-bit when the ImageBase is below 4GB 98 # (because the loader will never be rebase the image above 4GB). 99 if pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64: 100 if pe.OPTIONAL_HEADER.ImageBase <= 0xFFFFFFFF: 101 print("Checking %s ImageBase (0x%X < 4GB)... FAIL" % 102 (path, pe.OPTIONAL_HEADER.ImageBase)) 103 success = False 104 elif options.verbose: 105 print("Checking %s ImageBase (0x%X > 4GB)... PASS" % 106 (path, pe.OPTIONAL_HEADER.ImageBase)) 107 108 # Update tally. 109 if success: 110 pe_passed = pe_passed + 1 111 else: 112 failures.append(path) 113 114 print("Result: %d files found, %d files passed" % (pe_total, pe_passed)) 115 116 if options.json: 117 with open(options.json, 'w') as f: 118 json.dump(failures, f) 119 120 if pe_passed != pe_total: 121 sys.exit(1) 122 123if __name__ == '__main__': 124 usage = "Usage: %prog [options] DIRECTORY" 125 option_parser = optparse.OptionParser(usage=usage) 126 option_parser.add_option("-v", "--verbose", action="store_true", 127 default=False, help="Print debug logging") 128 option_parser.add_option("--json", help="Path to JSON output file") 129 options, args = option_parser.parse_args() 130 if not args: 131 option_parser.print_help() 132 sys.exit(0) 133 main(options, args) 134