Skip to content

Commit 68e3b72

Browse files
committed
check for address space layout randomization
1 parent bb60f49 commit 68e3b72

File tree

3 files changed

+207
-1
lines changed

3 files changed

+207
-1
lines changed

10_offensiveforensics/aslrcheck.py

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Requires volatility (the python 3 version)
2+
# pip install volatility3
3+
# Search all processes and check for ASLR protection
4+
from typing import Callable, List
5+
6+
from volatility.framework import constants, exceptions, interfaces, renderers
7+
from volatility.framework.configuration import requirements
8+
from volatility.framework.renderers import format_hints
9+
from volatility.framework.symbols import intermed
10+
from volatility.framework.symbols.windows import extensions
11+
from volatility.plugins.windows import pslist
12+
13+
import io
14+
import logging
15+
import pefile
16+
17+
vollog = logging.getLogger(__name__)
18+
19+
IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040
20+
IMAGE_FILE_RELOCS_STRIPPED = 0x0001
21+
22+
23+
def check_aslr(pe):
24+
pe.parse_data_directories(
25+
[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG"]]
26+
)
27+
dynamic = False
28+
stripped = False
29+
30+
if pe.OPTIONAL_HEADER.DllCharacteristics & IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE:
31+
dynamic = True
32+
if pe.FILE_HEADER.Characteristics & IMAGE_FILE_RELOCS_STRIPPED:
33+
stripped = True
34+
if not dynamic or (dynamic and stripped):
35+
aslr = False
36+
else:
37+
aslr = True
38+
return aslr
39+
40+
41+
class AslrCheck(interfaces.plugins.PluginInterface):
42+
@classmethod
43+
def get_requirements(cls):
44+
45+
return [
46+
requirements.TranslationLayerRequirement(
47+
name="primary",
48+
description="Memory layer for the kernel",
49+
architectures=["Intel32", "Intel64"],
50+
),
51+
requirements.SymbolTableRequirement(
52+
name="nt_symbols", description="Windows kernel symbols"
53+
),
54+
requirements.PluginRequirement(
55+
name="pslist", plugin=pslist.PsList, version=(1, 0, 0)
56+
),
57+
requirements.ListRequirement(
58+
name="pid",
59+
element_type=int,
60+
description="Process ID to include (all other processes are excluded)",
61+
optional=True,
62+
),
63+
]
64+
65+
@classmethod
66+
def create_pid_filter(
67+
cls, pid_list: List[int] = None
68+
) -> Callable[[interfaces.objects.ObjectInterface], bool]:
69+
filter_func = lambda _: False
70+
pid_list = pid_list or []
71+
filter_list = [x for x in pid_list if x is not None]
72+
if filter_list:
73+
filter_func = lambda x: x.UniqueProcessId not in filter_list
74+
return filter_func
75+
76+
def _generator(self, procs):
77+
pe_table_name = intermed.IntermediateSymbolTable.create(
78+
self.context,
79+
self.config_path,
80+
"windows",
81+
"pe",
82+
class_types=extensions.pe.class_types,
83+
)
84+
85+
procnames = list()
86+
for proc in procs:
87+
procname = proc.ImageFileName.cast(
88+
"string", max_length=proc.ImageFileName.vol.count, errors="replace"
89+
)
90+
if procname in procnames:
91+
continue
92+
procnames.append(procname)
93+
94+
proc_id = "Unknown"
95+
try:
96+
proc_id = proc.UniqueProcessId
97+
proc_layer_name = proc.add_process_layer()
98+
except exceptions.InvalidAddressException as e:
99+
vollog.error(
100+
f"Process {proc_id}: invalid address {e} in layer {e.layer_name}"
101+
)
102+
continue
103+
104+
peb = self.context.object(
105+
self.config["nt_symbols"] + constants.BANG + "_PEB",
106+
layer_name=proc_layer_name,
107+
offset=proc.Peb,
108+
)
109+
110+
try:
111+
dos_header = self.context.object(
112+
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
113+
offset=peb.ImageBaseAddress,
114+
layer_name=proc_layer_name,
115+
)
116+
except Exception as e:
117+
continue
118+
119+
pe_data = io.BytesIO()
120+
for offset, data in dos_header.reconstruct():
121+
pe_data.seek(offset)
122+
pe_data.write(data)
123+
pe_data_raw = pe_data.getvalue()
124+
pe_data.close()
125+
126+
try:
127+
pe = pefile.PE(data=pe_data_raw)
128+
except Exception as e:
129+
continue
130+
131+
aslr = check_aslr(pe)
132+
133+
yield (
134+
0,
135+
(
136+
proc_id,
137+
procname,
138+
format_hints.Hex(pe.OPTIONAL_HEADER.ImageBase),
139+
aslr,
140+
),
141+
)
142+
143+
def run(self):
144+
procs = pslist.PsList.list_processes(
145+
self.context,
146+
self.config["primary"],
147+
self.config["nt_symbols"],
148+
filter_func=self.create_pid_filter(self.config.get("pid", None)),
149+
)
150+
return renderers.TreeGrid(
151+
[
152+
("PID", int),
153+
("Filename", str),
154+
("Base", format_hints.Hex),
155+
("ASLR", bool),
156+
],
157+
self._generator(procs),
158+
)

poetry.lock

+48-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ lxml = "^4.7.1"
1616
pywin32 = "^303"
1717
pycryptodomex = "^3.12.0"
1818
WMI = "^1.5.1"
19+
volatility3 = "^1.0.1"
1920

2021
[tool.poetry.dev-dependencies]
2122
black = {version = "^21.12b0", allow-prereleases = true}

0 commit comments

Comments
 (0)