Skip to content

Commit d9fc072

Browse files
author
Michael Ligh
committed
push the 2019 plugin and analysis contest submissions
1 parent 4444474 commit d9fc072

File tree

19 files changed

+2177
-0
lines changed

19 files changed

+2177
-0
lines changed

AngeloMirabella/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Author: Angelo Mirabella
2+
3+
See https://github.com/Angelomirabella/linux_coredump for updates and license information.

BlaineStancill/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Author: BlaineStancill
2+
3+
See https://github.com/fireeye/win10_volatility for updates and license information.

CesarePizzi/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Author: Cesare Pizzi
609 KB
Binary file not shown.

CesarePizzi/powersh.py

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
# Volatility
2+
# Copyright (C) 2007-2013 Volatility Foundation
3+
# Copyright (C) 2009 Timothy D. Morgan (strings optimization)
4+
#
5+
# This file is part of Volatility.
6+
#
7+
# Volatility is free software; you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published by
9+
# the Free Software Foundation; either version 2 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# Volatility is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with Volatility. If not, see <http://www.gnu.org/licenses/>.
19+
#
20+
21+
import os
22+
import math
23+
import volatility.plugins.common as common
24+
import volatility.plugins.filescan as filescan
25+
import volatility.debug as debug
26+
import volatility.win32 as win32
27+
import volatility.utils as utils
28+
import volatility.obj as obj
29+
import volatility.exceptions as exceptions
30+
import volatility.constants as constants
31+
32+
try:
33+
import yara
34+
has_yara = True
35+
except ImportError:
36+
has_yara = False
37+
38+
39+
class PowerSh(common.AbstractWindowsCommand):
40+
"""Identify Powershell processes"""
41+
42+
def __init__(self, config, *args, **kwargs):
43+
common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs)
44+
45+
config.add_option("SCAN", short_option='S', default=False,
46+
action='store_true',
47+
help='Use PSScan instead of PSList')
48+
config.add_option("INSPECT-VAD", short_option='I', default=False,
49+
action='store_true',
50+
help='Inspect VAD for interesting powershell data')
51+
config.add_option("ENTROPY", short_option='E', default=3.0,
52+
action='store', type='float',
53+
help='Min Shannon Entropy used to identify meaningful strings')
54+
config.add_option("PRINTABLE", short_option='P', default=60,
55+
action='store', type='int',
56+
help='Min sequence of printable chars to consider it as meaningful strings')
57+
config.add_option('DUMP-DIR', short_option='D', default=None,
58+
cache_invalidator=False,
59+
help='Directory in which to dump interesting VAD files')
60+
config.add_option('MAX-SIZE', short_option='M', default=0x40000000,
61+
action='store', type='long',
62+
help='Set the maximum size (default is 1GB)')
63+
config.add_option('PID', short_option='p', default=None,
64+
help='Operate on these Process IDs (comma-separated)',
65+
action='store', type='str')
66+
config.add_option("UNSAFE", short_option="u",
67+
default=False, action='store_true',
68+
help='Bypasses certain sanity checks when creating image')
69+
config.add_option("MEMORY", short_option="m", default=False,
70+
action='store_true',
71+
help="Carve as a memory sample rather than exe/disk")
72+
config.add_option('FIX', short_option='x', default=False,
73+
help='Modify the image base of the dump to the in-memory base address',
74+
action='store_true')
75+
76+
def dump_pe(self, space, base):
77+
"""
78+
Dump a PE from an AS and return the content
79+
80+
@param space: an AS to use
81+
@param base: PE base address
82+
83+
@returns the PE file content
84+
"""
85+
86+
pe_file = obj.Object("_IMAGE_DOS_HEADER", offset=base, vm=space)
87+
88+
pe_map = []
89+
try:
90+
for offset, code in pe_file.get_image(unsafe=self._config.UNSAFE,
91+
memory=self._config.MEMORY,
92+
fix=self._config.FIX):
93+
pe_map = pe_map[:offset] + list(code) + pe_map[offset:]
94+
except ValueError, ve:
95+
result = "Error: {0}".format(ve)
96+
debug.warning(result)
97+
except exceptions.SanityCheckException, ve:
98+
result = "Error: {0} Try -u/--unsafe".format(ve)
99+
debug.warning(result)
100+
101+
return pe_map
102+
103+
def is_powershell(self, pe_file):
104+
"""
105+
Check if the process has the powershell.exe indicators.
106+
Can be updated and extended.
107+
108+
@param pe_file: the PE file carved from memory
109+
110+
@returns True or False accordingly with what has been found
111+
"""
112+
113+
result = False
114+
115+
# Define the indicators in YARA style :-)
116+
yara_powershell = 'rule powershell { \
117+
strings: \
118+
$str_Pdb = "powershell.pdb" \
119+
\
120+
$str_Int_Name = { 00 49 00 6E 00 74 00 65 \
121+
00 72 00 6E 00 61 00 6C \
122+
00 4E 00 61 00 6D 00 65 \
123+
00 00 00 50 00 4F 00 57 \
124+
00 45 00 52 00 53 00 48 \
125+
00 45 00 4C 00 4C \
126+
} \
127+
\
128+
$str_Description = { 46 00 69 00 6C 00 65 00 \
129+
44 00 65 00 73 00 63 00 \
130+
72 00 69 00 70 00 74 00 \
131+
69 00 6F 00 6E 00 00 00 \
132+
00 00 57 00 69 00 6E 00 \
133+
64 00 6F 00 77 00 73 00 \
134+
20 00 50 00 6F 00 77 00 \
135+
65 00 72 00 53 00 68 00 \
136+
65 00 6C 00 6C \
137+
} \
138+
\
139+
condition: \
140+
any of ($str*) }'
141+
142+
rules = yara.compile(source=yara_powershell)
143+
matched = rules.match(data=''.join(pe_file))
144+
if matched:
145+
result = True
146+
debug.debug("Powershell indicators found")
147+
148+
return result
149+
150+
def inspect_vad(self, path, vad, address_space):
151+
"""
152+
Read VAD and check it for interesting string. If found, dumps it to
153+
file. In order to avoid to hog RAM with huge VAD segments, everything
154+
will be dumped to a file, but only interesting one will be kept.
155+
156+
@param path: full path to output file
157+
@param vad: an MMVAD object
158+
@param address_space: process AS for the vad
159+
160+
@retruns: path of dumped memory or null if nothing has been found
161+
"""
162+
163+
# Here we search for printable string > 60 (configurable), simple but
164+
# effective
165+
yara_vad = 'rule vad {{ strings: $re1 = /[ -~]{{{0},}}/ condition: $re1 }}'.format(
166+
self._config.PRINTABLE)
167+
rules = yara.compile(source=yara_vad)
168+
169+
result = ""
170+
171+
fh = open(path, "wb")
172+
ever_matched = False
173+
174+
if fh:
175+
offset = vad.Start
176+
vad_end = offset + vad.Length
177+
while offset < vad_end:
178+
to_read = min(constants.SCAN_BLOCKSIZE, vad_end - offset)
179+
data = address_space.zread(offset, to_read)
180+
if not data:
181+
break
182+
fh.write(data)
183+
if ever_matched is False:
184+
matched = rules.match(data=data)
185+
if matched:
186+
# Check entropy of strings to dump only meaningful
187+
# sections
188+
debug.debug("Interesting VAD found")
189+
for s in matched[0].strings:
190+
if self.entropy(s[2]) > self._config.ENTROPY:
191+
ever_matched = True
192+
break
193+
194+
offset += to_read
195+
196+
fh.close()
197+
result = path
198+
else:
199+
debug.warning("Cannot open {0} for writing".format(path))
200+
result = ""
201+
202+
# If interesting strings has been found, keep the file, otherwise
203+
# drop everything
204+
if ever_matched is False:
205+
try:
206+
result = ""
207+
os.remove(path)
208+
except:
209+
debug.warning("Cannot remove file {0}".format(path))
210+
211+
return result
212+
213+
def entropy(self, string):
214+
"""
215+
Compute Shannon entropy of a string
216+
217+
@param string: string of which we have to calculate entropy
218+
219+
@returns: entropy
220+
"""
221+
222+
# Calculate probability
223+
probability = []
224+
for ch in dict.fromkeys(list(string)):
225+
probability.append(float(string.count(ch)) / len(string))
226+
227+
# Entropy
228+
ent = []
229+
for prob in probability:
230+
ent.append(prob * math.log(prob) / math.log(2.0))
231+
232+
return - sum(ent)
233+
234+
def filter_tasks(self, tasks):
235+
"""
236+
Reduce the tasks based on the user selectable PIDS parameter.
237+
238+
Returns a reduced list or the full list if config.PIDS not specified.
239+
"""
240+
241+
if self._config.PID is None:
242+
return tasks
243+
244+
try:
245+
pidlist = [int(p) for p in self._config.PID.split(',')]
246+
except ValueError:
247+
debug.error("Invalid PID {0}".format(self._config.PID))
248+
249+
return [t for t in tasks if t.UniqueProcessId in pidlist]
250+
251+
def calculate(self):
252+
253+
# Check if yara has been installed
254+
if not has_yara:
255+
debug.error("You must install yara to use this plugin")
256+
257+
# Starts listing all running processes, using the selected method
258+
tasks = []
259+
if self._config.SCAN:
260+
tasks = self.filter_tasks(
261+
list(filescan.PSScan(self._config).calculate()))
262+
else:
263+
addr_space = utils.load_as(self._config)
264+
tasks = self.filter_tasks(win32.tasks.pslist(addr_space))
265+
266+
procs = []
267+
dumped_files = []
268+
for task in tasks:
269+
task_space = task.get_process_address_space()
270+
if task_space is None:
271+
debug.warning(
272+
"Cannot acquire process AS for process {0}".format(task.ImageFileName))
273+
continue
274+
elif task.Peb is None:
275+
debug.warning("PEB at {0:#x} is not available (paging?) for process {1}".format(
276+
task.m('Peb'), task.m('ImageFileName')))
277+
continue
278+
elif task_space.vtop(task.Peb.ImageBaseAddress) is None:
279+
debug.warning("ImageBaseAddress at {0:#x} is not available (paging?) for process {1}".format(
280+
task.Peb.ImageBaseAddress, task.ImageFileName))
281+
continue
282+
else:
283+
# Extracts the file and checks the indicators of powershell
284+
debug.debug("Processing file {0}".format(
285+
task.m('ImageFileName')))
286+
pe_file = self.dump_pe(task_space, task.Peb.ImageBaseAddress)
287+
288+
if pe_file and self.is_powershell(pe_file):
289+
# Powershell found
290+
procs.append(task)
291+
292+
# Check for VAD inspection
293+
if self._config.INSPECT_VAD:
294+
if self._config.DUMP_DIR is None:
295+
debug.error(
296+
"Please specify a dump directory (--dump-dir)")
297+
if not os.path.isdir(self._config.DUMP_DIR):
298+
debug.error(self._config.DUMP_DIR +
299+
" is not a directory")
300+
301+
offset = task_space.vtop(task.obj_offset)
302+
# if this fails, we'll get its physical offset using kernel space
303+
if offset is None:
304+
offset = task.obj_vm.vtop(task.obj_offset)
305+
if offset is None:
306+
offset = 0
307+
308+
print "Inspecting VAD for " + str(task.UniqueProcessId)
309+
def filter(x): return x.Length < self._config.MAX_SIZE
310+
for vad, _aaddrspace in task.get_vads(vad_filter=filter, skip_max_commit=True):
311+
# Compose file name
312+
vad_start = self.format_value(
313+
vad.Start, "[addrpad]")
314+
vad_end = self.format_value(vad.End, "[addrpad]")
315+
316+
path = os.path.join(
317+
self._config.DUMP_DIR, "{0}.{1:x}.{2}-{3}.dmp".format(
318+
task.ImageFileName, offset, vad_start, vad_end))
319+
320+
dumped_files.append((task.UniqueProcessId,
321+
task.ImageFileName,
322+
vad.Start,
323+
vad.End,
324+
self.inspect_vad(path, vad, task_space)))
325+
326+
return procs, dumped_files
327+
328+
def render_text(self, outfd, data):
329+
330+
outfd.write(
331+
"\n\nPowershell indicators found in the following processes\n\n")
332+
333+
self.table_header(outfd,
334+
[("Pid", "10"),
335+
("Process", "20"),
336+
("Command Line", ""),
337+
])
338+
339+
for task in data[0]:
340+
341+
self.table_row(outfd,
342+
task.UniqueProcessId,
343+
task.ImageFileName,
344+
str(task.Peb.ProcessParameters.CommandLine or ''))
345+
346+
if self._config.INSPECT_VAD:
347+
outfd.write(
348+
"\n\nThe following VAD pages had interesting data:\n\n")
349+
350+
self.table_header(outfd,
351+
[("Pid", "10"),
352+
("Process", "20"),
353+
("Start", "[addrpad]"),
354+
("End", "[addrpad]"),
355+
("Result", ""),
356+
])
357+
358+
for dumped in data[1]:
359+
if dumped[4]: # Check if the path is defined
360+
self.table_row(outfd,
361+
dumped[0], # Process ID
362+
dumped[1], # Process Name
363+
dumped[2], # Start Address
364+
dumped[3], # End Address
365+
dumped[4]) # Path

ElmarNabigaev/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Author: Elmar Nabigaev

0 commit comments

Comments
 (0)