Skip to content

Commit 33855cf

Browse files
committed
Significantly improve the smear/error handling in the netstat plugin
1 parent b1a42d9 commit 33855cf

File tree

2 files changed

+120
-45
lines changed

2 files changed

+120
-45
lines changed

volatility3/framework/plugins/windows/netstat.py

Lines changed: 113 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,21 @@ def parse_bitmap(
111111
The list of indices at which a 1 was found.
112112
"""
113113
ret = []
114+
# This value is broken in many samples and was causing essentially infinite loops
115+
# Testing showed that 8192 is the current size across all Windows versions
116+
# We give some leeway in case it increases in later versions, while still keeping it sane
117+
# The problematic samples had values that looked like addresses, so in the billions
118+
if bitmap_size_in_byte > 8192 * 10:
119+
return ret
120+
114121
for idx in range(bitmap_size_in_byte):
115-
current_byte = context.layers[layer_name].read(bitmap_offset + idx, 1)[0]
122+
try:
123+
current_byte = context.layers[layer_name].read(bitmap_offset + idx, 1)[
124+
0
125+
]
126+
except exceptions.InvalidAddressException:
127+
continue
128+
116129
current_offs = idx * 8
117130
for bit in range(8):
118131
if current_byte & (1 << bit) != 0:
@@ -154,32 +167,37 @@ def enumerate_structures_by_port(
154167
)
155168
else:
156169
# invalid argument.
157-
return None
170+
return
158171

159172
vollog.debug(f"Current Port: {port}")
160173
# the given port serves as a shifted index into the port pool lists
161174
list_index = port >> 8
162175
truncated_port = port & 0xFF
163176

164-
# constructing port_pool object here so callers don't have to
165-
port_pool = context.object(
166-
net_symbol_table + constants.BANG + "_INET_PORT_POOL",
167-
layer_name=layer_name,
168-
offset=port_pool_addr,
169-
)
170-
171-
# first, grab the given port's PortAssignment (`_PORT_ASSIGNMENT`)
172-
inpa = port_pool.PortAssignments[list_index]
177+
try:
178+
# constructing port_pool object here so callers don't have to
179+
port_pool = context.object(
180+
net_symbol_table + constants.BANG + "_INET_PORT_POOL",
181+
layer_name=layer_name,
182+
offset=port_pool_addr,
183+
)
184+
# first, grab the given port's PortAssignment (`_PORT_ASSIGNMENT`)
185+
inpa = port_pool.PortAssignments[list_index]
173186

174-
# then parse the port assignment list (`_PORT_ASSIGNMENT_LIST`) and grab the correct entry
175-
assignment = inpa.InPaBigPoolBase.Assignments[truncated_port]
187+
# then parse the port assignment list (`_PORT_ASSIGNMENT_LIST`) and grab the correct entry
188+
assignment = inpa.InPaBigPoolBase.Assignments[truncated_port]
189+
except exceptions.InvalidAddressException:
190+
return
176191

177192
if not assignment:
178-
return None
193+
return
179194

180195
# the value within assignment.Entry is a) masked and b) points inside of the network object
181196
# first decode the pointer
182-
netw_inside = cls._decode_pointer(assignment.Entry)
197+
try:
198+
netw_inside = cls._decode_pointer(assignment.Entry)
199+
except exceptions.InvalidAddressException:
200+
return
183201

184202
if netw_inside:
185203
# if the value is valid, calculate the actual object address by subtracting the offset
@@ -188,16 +206,30 @@ def enumerate_structures_by_port(
188206
)
189207
yield curr_obj
190208

209+
try:
210+
next_obj_address = cls._decode_pointer(curr_obj.Next)
211+
except exceptions.InvalidAddressException:
212+
return
213+
191214
# if the same port is used on different interfaces multiple objects are created
192215
# those can be found by following the pointer within the object's `Next` field until it is empty
193-
while curr_obj.Next:
194-
curr_obj = context.object(
195-
obj_name,
196-
layer_name=layer_name,
197-
offset=cls._decode_pointer(curr_obj.Next) - ptr_offset,
198-
)
216+
while next_obj_address:
217+
try:
218+
curr_obj = context.object(
219+
obj_name,
220+
layer_name=layer_name,
221+
offset=next_obj_address - ptr_offset,
222+
)
223+
except exceptions.InvalidAddressException:
224+
return
225+
199226
yield curr_obj
200227

228+
try:
229+
next_obj_address = cls._decode_pointer(curr_obj.Next)
230+
except exceptions.InvalidAddressException:
231+
return
232+
201233
@classmethod
202234
def get_tcpip_module(
203235
cls,
@@ -243,16 +275,25 @@ def parse_hashtable(
243275
The hash table entries which are _not_ empty
244276
"""
245277
# we are looking for entries whose values are not their own address
278+
# smear sanity check from mass testing
279+
if ht_length > 4096:
280+
return
281+
246282
for index in range(ht_length):
247283
current_addr = ht_offset + index * alignment
248-
current_pointer = context.object(
249-
net_symbol_table + constants.BANG + "pointer",
250-
layer_name=layer_name,
251-
offset=current_addr,
252-
)
284+
try:
285+
current_pointer = context.object(
286+
net_symbol_table + constants.BANG + "pointer",
287+
layer_name=layer_name,
288+
offset=current_addr,
289+
)
290+
except exceptions.InvalidAddressException:
291+
continue
292+
253293
# check if addr of pointer is equal to the value pointed to
254294
if current_pointer.vol.offset == current_pointer:
255295
continue
296+
256297
yield current_pointer
257298

258299
@classmethod
@@ -292,22 +333,34 @@ def parse_partitions(
292333
tcpip_symbol_table + constants.BANG + "PartitionCount"
293334
).address
294335

295-
part_table_addr = context.object(
296-
net_symbol_table + constants.BANG + "pointer",
297-
layer_name=layer_name,
298-
offset=tcpip_module_offset + part_table_symbol,
299-
)
336+
try:
337+
part_table_addr = context.object(
338+
net_symbol_table + constants.BANG + "pointer",
339+
layer_name=layer_name,
340+
offset=tcpip_module_offset + part_table_symbol,
341+
)
342+
except exceptions.InvalidAddressException:
343+
vollog.debug(f"`PartitionTable` not present in memory.")
344+
return
300345

301346
# part_table is the actual partition table offset and consists out of a dynamic amount of _PARTITION objects
302347
part_table = context.object(
303348
net_symbol_table + constants.BANG + "_PARTITION_TABLE",
304349
layer_name=layer_name,
305350
offset=part_table_addr,
306351
)
307-
part_count = int.from_bytes(
308-
context.layers[layer_name].read(tcpip_module_offset + part_count_symbol, 1),
309-
"little",
310-
)
352+
353+
try:
354+
part_count = int.from_bytes(
355+
context.layers[layer_name].read(
356+
tcpip_module_offset + part_count_symbol, 1
357+
),
358+
"little",
359+
)
360+
except exceptions.InvalidAddressException:
361+
vollog.debug(f"`PartitionCount` not present in memory.")
362+
return
363+
311364
part_table.Partitions.count = part_count
312365

313366
vollog.debug(
@@ -316,9 +369,21 @@ def parse_partitions(
316369
entry_offset = context.symbol_space.get_type(obj_name).relative_child_offset(
317370
"ListEntry"
318371
)
319-
for ctr, partition in enumerate(part_table.Partitions):
372+
373+
try:
374+
partitions = part_table.Partitions
375+
except exceptions.InvalidAddressException:
376+
vollog.debug("Partitions member not present in memory")
377+
return
378+
379+
for ctr, partition in enumerate(partitions):
320380
vollog.debug(f"Parsing partition {ctr}")
321-
if partition.Endpoints.NumEntries > 0:
381+
try:
382+
num_entries = partition.Endpoints.NumEntries
383+
except exceptions.InvalidAddressException:
384+
continue
385+
386+
if num_entries > 0:
322387
for endpoint_entry in cls.parse_hashtable(
323388
context,
324389
layer_name,
@@ -402,6 +467,7 @@ def find_port_pools(
402467
upp_symbol = context.symbol_space.get_symbol(
403468
tcpip_symbol_table + constants.BANG + "UdpPortPool"
404469
).address
470+
405471
upp_addr = context.object(
406472
net_symbol_table + constants.BANG + "pointer",
407473
layer_name=layer_name,
@@ -498,13 +564,16 @@ def list_sockets(
498564

499565
# then, towards the UDP and TCP port pools
500566
# first, find their addresses
501-
upp_addr, tpp_addr = cls.find_port_pools(
502-
context,
503-
layer_name,
504-
net_symbol_table,
505-
tcpip_symbol_table,
506-
tcpip_module_offset,
507-
)
567+
try:
568+
upp_addr, tpp_addr = cls.find_port_pools(
569+
context,
570+
layer_name,
571+
net_symbol_table,
572+
tcpip_symbol_table,
573+
tcpip_module_offset,
574+
)
575+
except (exceptions.SymbolError, exceptions.InvalidAddressException):
576+
vollog.debug("Unable to reconstruct port pools")
508577

509578
# create port pool objects at the detected address and parse the port bitmap
510579
upp_obj = context.object(

volatility3/framework/symbols/windows/extensions/network.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,13 @@ def get_remote_address(self):
219219
return None
220220

221221
def is_valid(self):
222-
if self.State not in self.State.choices.values():
222+
# netstat calls this before validating the object itself
223+
try:
224+
state = self.State
225+
except exceptions.InvalidAddressException:
226+
return False
227+
228+
if state not in state.choices.values():
223229
vollog.debug(
224230
f"{type(self)} 0x{self.vol.offset:x} invalid due to invalid tcp state {self.State}"
225231
)

0 commit comments

Comments
 (0)