Check for running processes on Windows that have components that do not utilize ASLR
| #!/usr/bin/env python | |
| ''' | |
| Utility to check for processes running with non-ASLR-compatible components. | |
| Run with Administrative privileges to get visibility into all processes. | |
| (1a) psutil: https://pypi.org/project/psutil/ | |
| Installed via PIP | |
| -OR- | |
| (1b) Sysinternals ListDLLs: https://docs.microsoft.com/en-us/sysinternals/downloads/listdlls | |
| Installed in the Windows PATH | |
| (2a) pefile: https://github.com/erocarrera/pefile | |
| Installed via PIP or just by placing pefile.py in the same directory as this script | |
| -OR- | |
| (2b) Microsoft dumpbin.exe | |
| If testing a 64-bit version of Windows, then 64-bit Python will get most accurate results. | |
| 32-bit Python on 64-bit Windows will lack visibility into directories such as SYSWOW64 | |
| ''' | |
| import subprocess | |
| import os | |
| import sys | |
| import csv | |
| import struct | |
| import platform | |
| have_pefile = False | |
| have_psutil = False | |
| if struct.calcsize('P') == 4: | |
| # 32-bit Python | |
| if platform.machine() != 'x86': | |
| # Not 32-bit Windows | |
| print('You are running 32-bit Python on 64-bit Windows. Results may not be complete!') | |
| try: | |
| import pefile | |
| have_pefile = True | |
| except: | |
| print('Python pefile not found. Falling back to using dumpbin.exe...') | |
| try: | |
| import psutil | |
| have_psutil = True | |
| except: | |
| print('Python psutil not found. Falling back to using ListDLLs.exe...') | |
| IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040 | |
| IMAGE_FILE_RELOCS_STRIPPED = 0x0001 | |
| processes = {} | |
| all_libraries = [] | |
| library_headers = {} | |
| non_aslr_libraries = [] | |
| non_aslr_reasons = {} | |
| library_imagebase = {} | |
| results = [] | |
| csvoutput = 'checkaslr.csv' | |
| def check_aslr(libpath): | |
| DYNAMICBASE = False | |
| StrippedReloc = False | |
| dotnet = False | |
| wibu = False | |
| imagebase = 0 | |
| aslrcompat = True | |
| reason = '' | |
| if have_pefile: | |
| try: | |
| pe = pefile.PE(libpath, fast_load=True) | |
| pe.parse_data_directories([pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']]) | |
| if pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR']].VirtualAddress != 0: | |
| # .NET binary. These are relocated similarly to "Force ASLR", even without a relocation table | |
| dotnet = True | |
| if pe.sections[0].Name.decode('utf-8') == u'__wibu00': | |
| wibu = True | |
| if pe.FILE_HEADER.Characteristics & IMAGE_FILE_RELOCS_STRIPPED: | |
| StrippedReloc = True | |
| if pe.OPTIONAL_HEADER.DllCharacteristics & IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE: | |
| DYNAMICBASE = True | |
| if pe.OPTIONAL_HEADER.ImageBase: | |
| imagebase = hex(pe.OPTIONAL_HEADER.ImageBase).replace('L', '') | |
| if DYNAMICBASE and StrippedReloc: | |
| if dotnet: | |
| aslrcompat = False | |
| reason = '.NET code with relocations stripped is only relocated on Windows 8 or newer' | |
| else: | |
| aslrcompat = False | |
| reason = 'Relocations stripped' | |
| elif DYNAMICBASE and wibu: | |
| aslrcompat = False | |
| reason = 'WIBU-protected code may not be relocated.' | |
| elif dotnet: | |
| aslrcompat = True | |
| reason = '.NET code is always rebased on Windows 8 or later' | |
| elif not DYNAMICBASE: | |
| aslrcompat = False | |
| reason = 'No Dynamic Base' | |
| except: | |
| # Non-PE, bad permissions, etc... | |
| pass | |
| else: | |
| # no pefile. Fall back to dumpbin, which isn't as effective/complete | |
| try: | |
| header_info = subprocess.check_output(['dumpbin', '/headers', libpath.encode('utf-8')]) | |
| except subprocess.CalledProcessError as e: | |
| header_info = e.output | |
| if b'FILE HEADER VALUES' and b' image base (' not in header_info: | |
| # Not a library or otherwise not readable | |
| reason = 'dumpbin error' | |
| for line in header_info.splitlines(): | |
| line = line.strip() | |
| if line == b'Relocations stripped': | |
| StrippedReloc = True | |
| reason = 'Relocations stripped' | |
| elif line == b'Dynamic base': | |
| DYNAMICBASE = True | |
| elif b' image base (' in line: | |
| imagebase = '0x%s' % line.split()[0].decode('utf-8') | |
| elif b'RVA [size] of COM Descriptor Directory' in line: | |
| comdir = line.split()[0].decode('utf-8') | |
| if comdir != '0': | |
| dotnet = True | |
| elif line == b'__wibu00 name': | |
| wibu = True | |
| aslrcompat = False | |
| reason = 'WIBU-protected code may not be relocated.' | |
| if StrippedReloc and DYNAMICBASE: | |
| aslrcompat = False | |
| elif dotnet == True: | |
| aslrcompat = True | |
| reason = '.NET code is always rebased on Windows 8 or later' | |
| elif not DYNAMICBASE and reason == '': | |
| reason = 'No Dynamic base' | |
| aslrcompat = False | |
| return (aslrcompat, imagebase, reason) | |
| def get_modules(): | |
| if have_psutil: | |
| for proc in psutil.process_iter(): | |
| try: | |
| currentproc = proc.name() | |
| currentpid = proc.pid | |
| currentprocpath = ' '.join(proc.cmdline()) | |
| except: | |
| # Some processes aren't friendly | |
| continue | |
| processes[currentpid] = {} | |
| processes[currentpid]['proc'] = currentproc | |
| processes[currentpid]['libraries'] = [] | |
| processes[currentpid]['procpath'] = currentprocpath | |
| try: | |
| for library in proc.memory_maps(): | |
| if library.path not in all_libraries: | |
| all_libraries.append(library.path) | |
| processes[currentpid]['libraries'].append(library.path) | |
| except: | |
| # You can't get all maps | |
| pass | |
| else: | |
| # Fall back to using listdlls.exe | |
| dlls = subprocess.check_output(['listdlls']) | |
| #print(dlls) | |
| for line in dlls.splitlines(): | |
| if b' pid: ' in line: | |
| # Current PID | |
| splitline = line.split(b' pid: ') | |
| currentpid = splitline[-1].decode('utf-8') | |
| currentproc = splitline[0] | |
| #print(currentpid) | |
| #print(currentproc) | |
| processes[currentpid] = {} | |
| processes[currentpid]['proc'] = currentproc.decode('utf-8') | |
| processes[currentpid]['libraries'] = [] | |
| elif b'Command line: ' in line: | |
| # Command line | |
| currentprocpath = line.split(b'Command line: ')[-1].decode('utf-8') | |
| processes[currentpid]['procpath'] = currentprocpath | |
| #print(currentprocpath) | |
| elif b'0x' in line: | |
| # Loaded library | |
| #print(line) | |
| pathparts = line.split()[2:] | |
| if len(pathparts) == 1: | |
| path = pathparts[0].decode('utf-8') | |
| else: | |
| path = b' '.join(pathparts).decode('utf-8') | |
| # print(path) | |
| if path not in processes[currentpid]['libraries']: | |
| # ListDLLs sometimes shows duplicates | |
| processes[currentpid]['libraries'].append(path) | |
| if path not in all_libraries: | |
| all_libraries.append(path) | |
| return all_libraries | |
| all_libraries = get_modules() | |
| for library in all_libraries: | |
| #(aslr_compat, reason) = aslr_library(library) | |
| (aslr_compat, imagebase, reason) = check_aslr(library) | |
| if not aslr_compat: | |
| non_aslr_libraries.append(library) | |
| non_aslr_reasons[library] = reason | |
| library_imagebase[library] = imagebase | |
| for pid in processes: | |
| reason = '' | |
| aslr_incompat = False | |
| for library in processes[pid]['libraries']: | |
| if library in non_aslr_libraries: | |
| aslr_incompat = True | |
| if aslr_incompat and processes[pid]['proc'].lower() != 'listdlls.exe': | |
| print('*** %s (%s) is not ASLR compliant! ***' % (processes[pid]['proc'], pid)) | |
| print('Command line: %s' % processes[pid]['procpath']) | |
| print('Faulting module(s):') | |
| for library in processes[pid]['libraries']: | |
| if library in non_aslr_libraries: | |
| print('%s %s (%s)' %(library, library_imagebase[library], non_aslr_reasons[library])) | |
| result = {} | |
| result['proc'] = processes[pid]['proc'] | |
| result['pid'] = pid | |
| result['cmdline'] = processes[pid]['procpath'] | |
| result['module'] = library | |
| result['imagebase'] = library_imagebase[library] | |
| result['reason'] = non_aslr_reasons[library] | |
| results.append(result) | |
| print('') | |
| print('Writing output to %s ...' % csvoutput) | |
| if sys.version_info.major > 2: | |
| # Avoid newlines in CSV: https://bugs.python.org/issue30324 | |
| try: | |
| with open(csvoutput, 'w', newline='') as csvfile: | |
| writer = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) | |
| writer.writerow(['Process', 'PID', 'Command line', 'Module', 'Image Base', 'Reason']) | |
| for result in results: | |
| writer.writerow([result['proc'], result['pid'], result['cmdline'], result['module'],result['imagebase'],result['reason']]) | |
| except: | |
| print('Error writing to %s' % csvoutput) | |
| else: | |
| try: | |
| with open(csvoutput, 'wb') as csvfile: | |
| writer = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) | |
| writer.writerow(['Process', 'PID', 'Command line', 'Module', 'Image Base', 'Reason']) | |
| for result in results: | |
| writer.writerow([result['proc'], result['pid'], result['cmdline'], result['module'],result['imagebase'],result['reason']]) | |
| except: | |
| print('Error writing to %s' % csvoutput) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment