Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.