Last active
April 20, 2020 18:16
-
-
Save wdormann/0a6ee811627ba5610c945f4af4dd987f to your computer and use it in GitHub Desktop.
Check for running processes on Windows that have components that do not utilize ASLR
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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