Repository: aim4r/VolDiff
Branch: master
Commit: f52f15c4f892
Files: 3
Total size: 92.4 KB
Directory structure:
gitextract__n1rca3z/
├── LICENSE
├── README.md
└── VolDiff.py
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
Copyright (c) 2016, @aim4r
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
VolDiff: Malware Memory Footprint Analysis
==========================================
VolDiff is a Python script that leverages the [Volatility](https://github.com/volatilityfoundation/volatility) framework to identify malware threats on Windows 7 memory images.
VolDiff can be used to run a collection of Volatility plugins against memory images captured before and after malware execution. It creates a report that highlights system changes based on memory (RAM) analysis.
VolDiff can also be used against a single Windows memory image to automate Volatility plugin execution, and hunt for malicious patterns.
Installation and use directions
--------------------------------
Please refer to the VolDiff [home wiki](https://github.com/aim4r/VolDiff/wiki) for details. VolDiff has also been included in the [REMnux](https://remnux.org/) Linux malware analysis toolkit.
Sample report
--------------
See [this wiki page](https://github.com/aim4r/VolDiff/wiki/Memory-Analysis-of-DarkComet-using-VolDiff) for a sample VolDiff analysis of a system infected with the [DarkComet](https://en.wikipedia.org/wiki/DarkComet) RAT, or [this blog post](http://malwology.com/2015/06/25/remnux-v6-for-malware-analysis-part-1-voldiff/?utm_content=buffere3751&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer) for example VolDiff use againt a malware Trojan.
Inspiration
------------
This work was initially inspired by Andrew Case ([@attrc](https://twitter.com/attrc)) talk on [analyzing the sophisticated Careto malware sample with memory forensics](http://2014.video.sector.ca/video/110388398 "analyzing the sophisticated Careto malware sample with memory forensics"). Kudos to [@attrc](https://twitter.com/attrc) and all the [Volatility development team](https://github.com/aim4r/VolDiff/wiki#credits) for creating and maintaining the greatest memory forensic framework out there!
================================================
FILE: VolDiff.py
================================================
#!/usr/bin/env python
# VolDiff malware analysis script by @aim4r
__author__ = 'aim4r'
# IMPORTS ================================================================
import os
import sys
import time
import datetime
import difflib
import shutil
import re
import string
import simplejson
import urllib
import urllib2
import hashlib
from subprocess import Popen
# VARIABLES ================================================================
version = "2.1.5"
path_to_volatility = "vol.py"
max_concurrent_subprocesses = 3
diff_output_threshold = 100
ma_output_threshold = 60
vt_api_key = "473db868008cddb184e609ace3ca05de8e51f806e57eba74005aba2efa4a1e6e" # the rate limit os 4 requests per IP per minute
devnull = open(os.devnull, 'w')
# volatility plugins to run:
plugins_to_run = ["handles", "psxview", "netscan", "iehistory", "getsids", "pslist", "psscan", "cmdline", "consoles",
"dlllist", "filescan", "shimcache", "shellbags", "sessions", "messagehooks", "eventhooks", "svcscan",
"envars", "mutantscan", "symlinkscan", "atoms", "atomscan", "drivermodule", "mftparser", "driverscan",
"devicetree", "modules", "modscan", "unloadedmodules", "callbacks", "ldrmodules", "privs", "hashdump",
"threads", "malfind", "procdump", "idt", "gdt", "driverirp", "deskscan", "timers", "gditimers",
"ssdt"]
# volatility plugins to report / only used when a baseline memory image is provided:
plugins_to_report = ["pslist", "psscan", "psxview", "netscan", "iehistory", "malfind", "sessions", "privs",
"messagehooks", "eventhooks", "envars", "shimcache", "shellbags", "cmdline", "consoles",
"hashdump", "drivermodule", "driverscan", "driverirp", "modules", "modscan", "unloadedmodules",
"devicetree", "callbacks", "threads", "mutantscan", "symlinkscan", "ssdt"]
# REGEX EXPRESSIONS ================================================================
# regex expressions used to analyse imports
ransomware_imports = "CreateDesktop"
keylogger_imports = "GetKeyboardState|GetKeyState"
password_extract_imports = "SamLookupDomainInSamServer|NlpGetPrimaryCredential|LsaEnumerateLogonSessions|SamOpenDomain|SamOpenUser|SamGetPrivateData|SamConnect|SamRidToSid|PowerCreateRequest|SeDebugPrivilege|SystemFunction006|SystemFunction040"
clipboard_imports = "OpenClipboard"
process_injection_imports = "VirtualAllocEx|AllocateVirtualMemory|VirtualProtectEx|ProtectVirtualMemory|CreateProcess|LoadLibrary|LdrLoadDll|CreateToolhelp32Snapshot|QuerySystemInformation|EnumProcesses|WriteProcessMemory|WriteVirtualMemory|CreateRemoteThread|ResumeThread|SetThreadContext|SetContextThread|QueueUserAPC|QueueApcThread|WinExec|FindResource"
uac_bypass_imports = "AllocateAndInitializeSid|EqualSid|RtlQueryElevationFlags|GetTokenInformation|GetSidSubAuthority|GetSidSubAuthorityCount"
anti_debug_imports = "SetUnhandledExceptionFilter|CheckRemoteDebugger|DebugActiveProcess|FindWindow|GetLastError|GetWindowThreadProcessId|IsDebugged|IsDebuggerPresent|NtCreateThreadEx|NtGlobalFlags|NtSetInformationThread|OutputDebugString|pbIsPresent|Process32First|Process32Next|TerminateProcess|ThreadHideFromDebugger|UnhandledExceptionFilter|ZwQueryInformation|Sleep|GetProcessHeap"
web_imports = "InternetReadFile|recvfrom|WSARecv|DeleteUrlCacheEntry|CreateUrlCacheEntry|URLDownloadToFile|WSASocket|WSASend|WSARecv|WS2_32|InternetOpen|HTTPOpen|HTTPSend|InternetWrite|InternetConnect"
listen_imports = "RasPortListen|RpcServerListen|RpcMgmtWaitServerListen|RpcMgmtIsServerListening"
service_imports = "OpenService|CreateService|StartService|NdrClientCall2|NtLoadDriver"
shutdown_imports = "ExitWindows"
registry_imports = "RegOpenKey|RegQueryValue|ZwSetValueKey"
file_imports = "CreateFile|WriteFile"
atoms_imports = "GlobalAddAtom"
localtime_imports = "GetLocalTime|GetSystemTime"
driver_imports = "DeviceIoControl"
username_imports = "GetUserName|LookupAccountNameLocal"
machine_version_imports = "GetVersion"
startup_imports = "GetStartupInfo"
diskspace_imports = "GetDiskFreeSpace"
sysinfo_imports = "CreateToolhelp32Snapshot|NtSetSystemInformation|NtQuerySystemInformation|GetCurrentProcess|GetModuleFileName"
# regex expressions used to analyse strings (from process executables)
web_regex_str = "cookie|download|proxy|responsetext|socket|useragent|user-agent|urlmon|user_agent|WebClient|winhttp|http"
antivirus_regex_str = "antivir|anvir|avast|avcons|avgctrl|avginternet|avira|bitdefender|checkpoint|comodo|F-Secure|firewall|kaspersky|mcafee|norton|norman|safeweb|sophos|symantec|windefend"
virtualisation_regex_str = "000569|001C14|080027|citrix|parallels|proxmox|qemu|SbieDll|Vbox|VMXh|virm|virtualbox|virtualpc|vmsrvc|vpc|winice|vmware|xen"
sandbox_regex_str = "anubis|capturebat|cuckoo|deepfreeze|debug|fiddler|fireeye|inctrl5|installwatch|installspy|netmon|noriben|nwinvestigatorpe|perl|processhacker|python|regshot|sandb|schmidti|sleep|snort|systracer|uninstalltool|tcpdump|trackwinstall|whatchanged|wireshark"
sysinternals_regex_str = "filemon|sysinternal|procdump|procexp|procmon|psexec|regmon|sysmon"
shell_regex_str = "shellexecute|shell32"
keylogger_regex_str = "backspace|klog|keylog|shift"
filepath_regex_str = 'C:\\\(?:[^\\\/:*?"<>|\r\n]+\\\)*[^\\\/:*?"<>|\r\n]*'
password_regex_str = "brute|credential|creds|mimikatz|passwd|password|pwd|sniff|stdapi|WCEServicePipe|wce_krbtkts"
powershell_regex_str = "powerview|powershell"
sql_regex_str = "SELECT|INSERT|sqlite|MySQL"
infogathering_regex_str = "driverquery|gethost|wmic|GetVolumeInformation|systeminfo|tasklist|reg.exe"
tool_regex_str = "cain|clearev|ipscan|netsh|rundll32|timestomp|torrent"
banking_regex_str = "banc|banco|bank|Barclays|hsbc|jpmorgan|lloyds|natwest|nwolb|paypal|rbc.com|santander"
socialsites_regex_str = "facebook|instagram|linkedin|pastebin|twitter|yahoo|youtube"
exec_regex_str = ".*\.bat|.*\.cmd|.*\.class|.*\.exe|.*\.jar|.*\.js|.*\.jse|.*\.SCR|.*\.VBE|.*\.vbs"
crypto_regex_str = "bitlocker|bitcoin|CIPHER|crypt|locker|logkey|publickey|ransom|truecrypt|veracrypt"
rat_regex_str = "backdoor|botnet|login|malware|rootkit|screenshot|Trojan|Vnc|VncStart"
browser_regex_str = "chrome|firefox|mozilla|opera"
other_regex_str = "admin|currentversion|hosts|registry|smtp|UserInit|.*\.pdb"
# regex expressions used to extract ips, domains and email addresses
ips_regex = r"(?!\b\d{1,3}\.\d{1,3}\.\d{1,3}\.0\b)\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"
domains_regex_http = 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
domains_regex_ftp = 'ftp[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
domains_regex_file = 'file[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
emails_regex = r"\b[a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+\b"
# regex expressions used to analyse registry handles
registry_infogathering_regex = "SOFTWARE\\\MICROSOFT|Parameters|SOFTWARE\\\POLICIES"
registry_proxy_settings_regex = "INTERNET SETTINGS"
registry_locale_regex = "NLS\\\LOCALE"
registry_hostname_regex = "COMPUTERNAME"
registry_installed_programs_regex = "CurrentVersion\\\App Paths|CurrentVersion\\\Uninstall|Installed Components"
registry_remote_control_regex = "Terminal Server|Realvnc"
registry_firewall_regex = "firewall"
registry_services_regex = "CurrentControlSet\\\services"
registry_network_regex = "NetworkList|Tcpip"
registry_autorun_regex = "CurrentVersion\\\Explorer|CurrentVersion\\\Run|CurrentVersion\\\Windows|Current Version\\\Policies\\\Explorer|CurrentVersion\\\Winlogon|Shell Extensions"
registry_command_processor_regex = "Command Processor"
registry_crypto_regex = "CRYPTOGRAPHY"
registry_tracing_regex = "TRACING"
registry_file_associations_regex = "SYSTEMFILEASSOCIATIONS"
registry_ie_security_regex = "INTERNET EXPLORER\\\SECURITY"
registry_security_center_regex = "Security Center"
# suspicious processes and dlls
hacker_process_regex = "at.exe|chtask.exe|clearev|ftp.exe|net.exe|nbtstat.exe|net1.exe|ping.exe|powershell|procdump.exe|psexec|quser.exe|reg.exe|regsvr32.exe|schtasks|systeminfo.exe|taskkill.exe|timestomp|winrm|wmic|xcopy.exe"
hacker_dll_regex = "mimilib.dll|sekurlsa.dll|wceaux.dll|iamdll.dll|VMCheck.dll"
# suspicious process names
l33t_process_name = "snss|crss|cssrs|csrsss|lass|isass|lssass|lsasss|scvh|svch0st|svhos|svchst|svchosts|lsn|g0n|l0g|nvcpl|rundii|wauclt|spscv|spppsvc|sppscv|sppcsv|taskchost|tskhost|msorsv|corsw|arch1ndex|wmipvr|wmiprse|runddl|crss.exe"
# "usual" process names
usual_processes = "sppsvc.exe|audiodg.exe|mscorsvw.exe|SearchIndexer|TPAutoConnSvc|TPAutoConnect|taskhost.exe|smss.exe|wininit.exe|services.exe|lsass.exe|svchost.exe|lsm.exe|explorer.exe|winlogon|conhost.exe|dllhost.exe|spoolsv.exe|vmtoolsd.exe|WmiPrvSE.exe|msdtc.exe|TrustedInstall|SearchFilterHo|csrss.exe|System|ipconfig.exe|cmd.exe|dwm.exe|mobsync.exe|DumpIt.exe|VMwareTray.exe|wuauclt.exe|LogonUI.exe|SearchProtocol|vssvc.exe|WMIADAP.exe"
# suspicious filepaths
susp_filepath = "\\\ProgramData|\\\Recycle|\\\Windows\\\Temp|\\\Users\\\All|\\\Users\\\Default|\\\Users\\\Public|\\\ProgramData|AppData"
temp_filepath = "\\\TMP|\\\TEMP|\\\AppData"
# usual timers
usual_timers = "ataport.SYS|ntoskrnl.exe|NETIO.SYS|storport.sys|afd.sys|cng.sys|dfsc.sys|discache.sys|HTTP.sys|luafv.sys|ndis.sys|Ntfs.sys|rdbss.sys|rdyboost.sys|spsys.sys|srvnet.sys|srv.sys|tcpip.sys|usbccgp.sys|netbt.sys|volsnap.sys|dxgkrnl.sys|bowser.sys|fltmgr.sys"
# usual gditimers
usual_gditimers = "dllhost.exe|explorer.exe|csrss.exe"
# usual ssdt
usual_ssdt = "(ntos|win32k)"
# usual atoms and atomscan dlls
usual_atoms_dlls = "system32\\\wls0wndh.dll|System32\\\pnidui.dll|system32\\\stobject.dll|vmusr\\\\vmtray.dll|system32\\\EXPLORERFRAME.dll|system32\\\uxtheme.dll|system32\\\MsftEdit.dll|system32\\\SndVolSSO.DLL|system32\\\\fxsst.dll|system32\\\WINMM.dll"
# extensions of interest
susp_extensions_regex = "\.job$|\.pdb$|\.xls$|\.doc$|\.pdf$|\.tmp$|\.temp$|\.rar$|\.zip$|\.bat|\.cmd$|\.class$|\.jar$|\.jse$|\.SCR$|\.VBE$|\.vbs$"
# DICTIONARIES/LISTS USED FOR PROCESS CHECKS ================================================================
# list of "unique" processes
uniq_processes = ["services.exe", "System", "wininit.exe", "smss.exe", "lsass.exe", "lsm.exe", "explorer.exe"]
# expected execution path for some processes
process_execpath = {'smss.exe': "\systemroot\system32\smss.exe",
"crss.exe": "\windows\system32\csrss.exe",
"wininit.exe": "wininit.exe",
"services.exe": "\windows\system32\services.exe",
"lsass.exe": "\windows\system32\lsass.exe",
"svchost.exe": "\windows\system32\svchost.exe",
"lsm.exe": "\windows\system32\lsm.exe",
"explorer.exe": "\windows\explorer.exe",
"winlogon.exe": "winlogon.exe",
"sppsvc.exe": "\windows\system32\sppsvc.exe"}
# expected process parent/child relationship
parent_child = {'services.exe': ["sppsvc.exe", "taskhost.exe", "mscorsvw.exe", "TPAutoConnSvc", "SearchIndexer", "svchost.exe", "taskhost.exe", "spoolsv.exe"],
'System': ["smss.exe"], 'csrss.exe': ["conhost.exe"],
'svchost.exe': ["WmiPrvSE.exe", "audiodg.exe"],
'wininit.exe': ["services.exe", "lsass.exe", "lsm.exe"]
}
# expected process sessions
session0_processes = ["wininit.exe", "services.exe", "svchost.exe", "lsm.exe", "lsass.exe"]
session1_processes = ["winlogon.exe"]
# VOLATILITY PROFILES ================================================================
profiles = ["VistaSP0x86", "VistaSP0x64", "VistaSP1x86", "VistaSP1x64", "VistaSP2x86", "VistaSP2x64",
"Win2003SP0x86", "Win2003SP1x86", "Win2003SP1x64", "Win2003SP2x86", "Win2003SP2x64",
"Win2008SP1x86", "Win2008SP1x64", "Win2008SP2x86", "Win2008SP2x64", "Win2008R2SP0x64", "Win2008R2SP1x64",
"Win2012R2x64", "Win2012x64",
"Win7SP0x86", "Win7SP0x64", "Win7SP1x86", "Win7SP1x64",
"Win8SP0x86", "Win8SP0x64", "Win8SP1x86", "Win8SP1x64",
"WinXPSP2x86", "WinXPSP1x64", "WinXPSP2x64", "WinXPSP3x86"]
preferred_profiles = ["Win7SP0x86", "Win7SP0x64", "Win7SP1x86", "Win7SP1x64"]
# PRINT VOLDIFF BANNER ================================================================
def print_voldiff_banner():
print (" _ ___ _ __ __ ")
print (" /\ /\___ | | / (_)/ _|/ _|")
print (" \ \ / / _ \| | / /\ / | |_| |_ ")
print (" \ V / (_) | |/ /_//| | _| _|")
print (" \_/ \___/|_/___,' |_|_| |_| ")
print ("\nVolDiff: Malware Memory Footprint Analysis (v%s)\n" % version)
# PRINT HELP SECTION ================================================================
def print_help():
print ("Usage: ./VolDiff.py [BASELINE_IMAGE] INFECTED_IMAGE PROFILE [OPTIONS]")
print ("\nOptions:")
print ("--help display this help and exit")
print ("--version display version information and exit")
print ("--dependencies display information about script dependencies and exit")
print ("--malware-checks hunt and report suspicious anomalies (slow, recommended)")
print ("--output-dir [dir] custom directory to store analysis results")
print ("--no-report do not create a report")
print ("\nTested using Volatility 2.5 (vol.py) on Windows 7 images.")
sys.exit()
# PRINT VERSION INFORMATION ================================================================
def print_version():
print ("This is a free software: you are free to change and redistribute it.")
print ("There is no warranty, to the extent permitted by law.")
print ("Written by @aim4r. Report bugs to voldiff[@]gmail.com.")
sys.exit()
# PRINT DEPENDENCIES ================================================================
def print_dependencies():
print ("Requires volatility 2.5 (vol.py) to be installed.")
sys.exit()
# VERIFY PATH TO VOLATILITY EXISTS ================================================================
def check_volatility_path(path):
if os.path.isfile(path):
return True
for p in os.environ["PATH"].split(os.pathsep):
full_path = os.path.join(p, path)
if os.path.exists(full_path):
return True
return False
# VERIFY ENOUGH ARGUMENTS ARE SUPPLIED ================================================================
def check_enough_arguments_supplied(n=4):
if len(sys.argv) < n:
print("Not enough arguments supplied. Please use the --help option for help.")
sys.exit()
# SET PROFILE AND FIND PATH TO MEMORY IMAGE(S) ================================================================
def check_profile(pr):
if pr not in profiles:
print ("Please specify a valid Volatility Windows profile for use (such as Win7SP1x64).")
sys.exit()
if pr not in preferred_profiles:
print(
"WARNING: This script was only tested using Windows 7 profiles. The specified profile (%s) seems different!" % pr)
else:
print ("Profile: %s" % pr)
return
# COMPLETION AND CLEANUP FUNCTION ================================================================
def script_completion(start_time):
if 'report' in globals():
report.write("\n\nEnd of report.")
report.close()
open_report(output_dir + "/VolDiff_Report.txt")
notify_completion("VolDiff execution completed.")
shutil.rmtree(output_dir + '/tmpfolder')
completion_time = time.time() - start_time
a = int(completion_time / 60)
b = int(completion_time % 60)
if 'devnull' in globals():
devnull.close()
print("\nVolDiff execution completed in %s minutes and %s seconds." % (a, b))
sys.exit()
# DIFFING RESULTS ================================================================
def diff_files(path1, path2, diffpath):
with open(path1, "r") as file1:
with open(path2, "r") as file2:
diff = difflib.unified_diff(file1.readlines(), file2.readlines())
with open(diffpath, 'w+') as file3:
print >> file3, ''.join(list(diff))
file3.seek(0)
lines = file3.readlines()
file3.seek(0)
for line in lines:
if line.startswith("+") and not line.startswith("+++"):
file3.write(line[1:])
file3.truncate()
return
# REPORT CREATION ================================================================
def report_plugin(plugin, header_lines=0, threshold=diff_output_threshold):
report.write("\n\nNew %s entries." % plugin)
report.write("\n==========================================================================================================================\n")
if header_lines != 0:
with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f:
for i in range(header_lines):
line = next(f, '').strip()
report.write(line + "\n")
line_counter = 0
with open(output_dir + "/" + plugin + "/diff_" + plugin + ".txt") as diff:
for line in diff:
line_counter += 1
if line_counter < threshold:
report.write(line)
else:
report.write("\nWarning: too many new entries to report, output truncated!\n")
break
return
# OPENING REPORT ================================================================
def open_report(report_path):
if os.name == 'posix':
p = Popen(['xdg-open', report_path], stdout=devnull, stderr=devnull)
p.wait()
elif os.name == 'mac':
p = Popen(['open', report_path], stdout=devnull, stderr=devnull)
p.wait()
elif os.name == 'nt':
p = Popen(['cmd', '/c', 'start', report_path], stdout=devnull, stderr=devnull) # cmd /c start [filename]
p.wait()
# NOTIFYING ABOUT SCRIPT COMPLETION ================================================================
def notify_completion(message):
if os.name == 'posix':
p = Popen(['notify-send', message], stdout=devnull, stderr=devnull)
p.wait()
# MALWARE ANALYSIS FUNCTIONS ================================================================
def open_full_plugin(plugin="psscan", lines_to_ignore=2, state="infected"):
if os.path.isfile(output_dir + "/" + plugin + "/" + state + "_" + plugin + ".txt"):
f = open(output_dir + "/" + plugin + "/" + state + "_" + plugin + ".txt")
else:
f = open(output_dir + "/" + plugin + "/" + plugin + ".txt")
for i in xrange(lines_to_ignore):
next(f, '')
return f
def open_diff_plugin(plugin="psscan", lines_to_ignore=2):
if os.path.isfile(output_dir + "/" + plugin + "/diff_" + plugin + ".txt"):
f = open(output_dir + "/" + plugin + "/diff_" + plugin + ".txt")
else:
f = open(output_dir + "/" + plugin + "/" + plugin + ".txt")
for i in xrange(lines_to_ignore):
next(f, '')
return f
def anomaly_search(plugin, regex_to_include, ignorecase='yes', regex_to_exclude='', diff="diff"):
match_list = []
if diff == "diff":
f = open_diff_plugin(plugin)
else:
f = open_full_plugin(plugin)
for line in f:
if ignorecase == 'yes':
if re.search(regex_to_include, line, re.IGNORECASE):
if regex_to_exclude == '':
match_list.append(line)
elif not re.search(regex_to_exclude, line, re.IGNORECASE):
match_list.append(line)
else:
if re.search(regex_to_include, line):
if regex_to_exclude == '':
match_list.append(line)
elif not re.search(regex_to_exclude, line):
match_list.append(line)
f.close()
return match_list
def anomaly_search_inverted(plugin, regex_to_exclude, ignorecase='yes', regex_to_include=''):
match_list = []
f = open_diff_plugin(plugin)
for line in f:
if ignorecase == 'yes':
if not re.search(regex_to_exclude, line, re.IGNORECASE):
if regex_to_include == '':
match_list.append(line)
elif re.search(regex_to_include, line, re.IGNORECASE):
match_list.append(line)
else:
if not re.search(regex_to_exclude, line):
if regex_to_include == '':
match_list.append(line)
elif re.search(regex_to_include, line):
match_list.append(line)
f.close()
return match_list
def report_anomalies(headline, anomaly_list, delim="=", plugin="", header_lines=0, threshold=ma_output_threshold):
if len(anomaly_list) != 0:
report.write("\n\n%s" % headline)
if delim == "=":
report.write(
"\n==========================================================================================================================\n")
elif delim == '-':
report.write(
"\n--------------------------------------------------------------------------------------------------------------------------\n")
if header_lines != 0 and plugin != "":
if os.path.isfile(output_dir + "/" + plugin + "/infected_" + plugin + ".txt"):
with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f:
for i in range(header_lines):
line = next(f, '').strip()
report.write(line + "\n")
else:
with open(output_dir + "/" + plugin + "/" + plugin + ".txt") as f:
for i in range(header_lines):
line = next(f, '').strip()
report.write(line + "\n")
if len(anomaly_list) > threshold:
anomaly_list_to_report = anomaly_list[0:threshold]
anomaly_list_to_report.append("\nWarning: too many entries to report, output truncated!\n")
else:
anomaly_list_to_report = anomaly_list
for line in anomaly_list_to_report:
report.write(line)
return
def extract_substrings(input_list, regex):
extracted_list = []
for entry in input_list:
subentries = entry.split(' ')
for subentry in subentries:
if re.search(regex, subentry, re.IGNORECASE):
extracted_list.append(subentry)
return extracted_list
def tidy_list(input_list):
updatedlist = []
for entry in input_list:
if not re.search("\\n", entry):
entry += '\n'
updatedlist.append(entry)
updatedlist = sorted(set(updatedlist))
return updatedlist
def find_ips_domains_emails(plugin):
f = open_diff_plugin(plugin, 0)
ips = []
ips_to_report = []
ips_regex_exclude = r"127\.0\.0\.1|0\.0\.0\.0"
for line in f:
if re.search(ips_regex, line, re.IGNORECASE):
ips += re.findall(ips_regex, line, re.IGNORECASE)
for ip in ips:
if not re.search(ips_regex_exclude, ip, re.IGNORECASE):
ips_to_report.append(ip)
domains = []
f.seek(0)
for line in f:
if re.search(domains_regex_http, line, re.IGNORECASE):
domains += re.findall(domains_regex_http, line, re.IGNORECASE)
if re.search(domains_regex_ftp, line, re.IGNORECASE):
domains += re.findall(domains_regex_ftp, line, re.IGNORECASE)
if re.search(domains_regex_file, line, re.IGNORECASE):
domains += re.findall(domains_regex_file, line, re.IGNORECASE)
emails = []
f.seek(0)
for line in f:
if re.search(emails_regex, line, re.IGNORECASE):
emails += re.findall(emails_regex, line, re.IGNORECASE)
ips_domains_emails = ips_to_report + domains + emails
ips_domains_emails = tidy_list(ips_domains_emails)
f.close()
return ips_domains_emails
def get_pids(procname, plugin="psscan"):
pids = []
if procname == "":
return pids
f = open_full_plugin(plugin, 2)
for line in f:
if re.search(' ' + procname + ' ', line, re.IGNORECASE):
pids.append(re.sub(' +', ' ', line).split(' ')[2])
pids = sorted(set(pids))
f.close()
return pids
def get_associated_process_lines_pids(pids, plugin="psscan"):
f = open_full_plugin(plugin, 2)
associated_psscan_lines = []
for line in f:
for pid in pids:
if re.sub(' +', ' ', line).split(' ')[2] == str(pid):
associated_psscan_lines.append(line)
f.close()
return associated_psscan_lines
def get_associated_process_lines_ppids(ppids, plugin="psscan"):
f = open_full_plugin(plugin, 2)
associated_psscan_lines = []
for line in f:
for ppid in ppids:
if re.sub(' +', ' ', line).split(' ')[3] == str(ppid):
associated_psscan_lines.append(line)
f.close()
return associated_psscan_lines
def get_childs_of(pids):
f = open_full_plugin("psscan", 2)
childs = []
for line in f:
for pid in pids:
ppid = re.sub(' +', ' ', line).split(' ')[3]
if ppid == str(pid):
childs.append(re.sub(' +', ' ', line).split(' ')[2])
childs = sorted(set(childs))
f.close()
return childs
def get_parent_pids_of(childs):
f = open_full_plugin("psscan", 2)
parents = []
for line in f:
for child in childs:
if re.sub(' +', ' ', line).split(' ')[2] == child:
parents.append(re.sub(' +', ' ', line).split(' ')[3])
parents = sorted(set(parents))
f.close()
return parents
def get_procnames(pids):
f = open_full_plugin("psscan", 2)
procnames = []
for line in f:
for pid in pids:
if re.sub(' +', ' ', line).split(' ')[2] == pid:
procnames.append(re.sub(' +', ' ', line).split(' ')[1])
f.close()
return procnames
def get_all_pids(exception_regex=''):
f = open_full_plugin("psscan", 2)
pids = []
for line in f:
if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE):
continue
else:
pid = re.sub(' +', ' ', line).split(' ')[2]
if pid != "0":
pids.append(pid)
pids = sorted(set(pids))
f.close()
return pids
def get_diff_pids(exception_regex=''):
f = open_diff_plugin("psscan", 2)
pids = []
for line in f:
if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE):
continue
else:
pid = re.sub(' +', ' ', line).split(' ')[2]
if pid != "0":
pids.append(pid)
pids = sorted(set(pids))
f.close()
return pids
def get_procname(pid, plugin='psscan'):
f = open_full_plugin(plugin, 2)
procnamee = ""
for line in f:
if re.search(r"[a-zA-Z\.]\s+%s " % pid, line, re.IGNORECASE):
procnamee = (re.sub(' +', ' ', line).split(' ')[1])
break
procnamee = str(procnamee)
f.close()
return procnamee
def get_all_procnames(plugin='psscan', exception_regex=''):
f = open_full_plugin(plugin, 2)
procnames = []
for line in f:
if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE):
continue
else:
procnames.append(re.sub(' +', ' ', line).split(' ')[1])
procnames = sorted(set(procnames))
f.close()
return procnames
def get_all_ppids(exception_regex=''):
f = open_full_plugin("psscan", 2)
ppids = []
for line in f:
if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE):
continue
elif re.sub(' +', ' ', line).split(' ')[2] != "0":
ppids.append(re.sub(' +', ' ', line).split(' ')[3])
ppids = sorted(set(ppids))
f.close()
return ppids
def get_session(pid):
session = ""
f = open_full_plugin("pslist", 2)
for line in f:
if re.search(' ' + str(pid) + ' ', line, re.IGNORECASE):
session = re.sub(' +', ' ', line).split(' ')[6]
break
f.close()
return session
def get_execpath(pid):
execpath = ''
procnamep = get_procname(pid)
f = open_full_plugin("dlllist", 0)
for line in f:
if re.search(procnamep + ' pid.*' + str(pid), line, re.IGNORECASE):
command_line = next(f, '')
execpath = re.sub("Command line : ", "", command_line)
execpath = re.sub(".:", "", execpath)
execpath = re.sub(" .*", "", execpath)
execpath = re.sub("\n", "", execpath)
f.close()
return execpath
def get_cmdline(pid):
cmdline = []
procnamec = get_procname(pid)
f = open_full_plugin("cmdline", 0)
for line in f:
if re.search(procnamec + ' pid.* ' + pid, line, re.IGNORECASE):
cmdline.append(line)
line = next(f, '')
cmdline.append(line)
break
if cmdline:
if not re.search("Command", cmdline[1], re.IGNORECASE):
cmdline = []
f.close()
return cmdline
def deadproc_activethreads():
f = open_full_plugin("psxview", 2)
dead_proc_active_threads = []
for line in f:
if 'UTC' in str(re.sub(' +', ' ', line).split(' ')[9:]) and re.sub(' +', ' ', line).split(' ')[5] == "True":
dead_proc_active_threads.append(line)
f.close()
return dead_proc_active_threads
def get_hosts_contents(memory_image_file):
hostscontent = []
f = open_full_plugin("filescan", 2)
qaddressb = ""
for line in f:
if re.search("etc\\\hosts$", line, re.IGNORECASE):
qaddressb = re.sub(' +', ' ', line).split(' ')[0]
break
if qaddressb != "":
hostsfolder = tmpfolder + "hosts/"
if not os.path.isdir(hostsfolder):
os.makedirs(hostsfolder)
process_var = Popen([path_to_volatility, "--profile", profile, "-f", memory_image_file, "dumpfiles", "-Q", qaddressb, "-D", hostsfolder], stdout=devnull, stderr=devnull)
process_var.wait()
dumped_hosts_filename = os.listdir(hostsfolder)
if len(dumped_hosts_filename) == 1:
with open(hostsfolder + str(dumped_hosts_filename[0]), mode='rb') as hosts:
for line in hosts:
if not re.search("^#", line) and re.search(" ", line):
hostscontent.append(line)
hostscontent = sorted(set(hostscontent))
f.close()
return hostscontent
def filter_new_services():
filtered_services = []
diff_svcscan = open_diff_plugin("svcscan", 0)
baseline_svcscan = open_full_plugin("svcscan", 0, "baseline")
for line in diff_svcscan:
if line not in baseline_svcscan and not re.search("Offset:", line, re.IGNORECASE):
filtered_services.append(line)
baseline_svcscan.seek(0)
filtered_services = set(filtered_services)
baseline_svcscan.close()
diff_svcscan.close()
return filtered_services
def get_associated_services(pid):
services = []
full_svcscan = open_full_plugin("svcscan", 0)
for line in full_svcscan:
if re.search("Process ID: " + str(pid) + "\n", line, re.IGNORECASE):
services.append("\n")
services.append(line)
for i in xrange(5):
line = next(full_svcscan, '')
services.append(line)
full_svcscan.close()
return services
def get_malfind_pids():
malfind_pids = []
f = open_diff_plugin("malfind", 0)
for line in f:
if re.search("Address:", line):
malfind_pids.append(re.sub(' +', ' ', line).split(' ')[3])
malfind_pids = sorted(set(malfind_pids))
f.close()
return malfind_pids
def get_malfind_injections(pid, m="dual"):
malfind_injections = []
f = open_diff_plugin("malfind", 0)
if m == "dual":
n = 6
else:
n = 7
for line in f:
if re.search("Pid: " + str(pid) + " ", line):
malfind_injections.append("\n")
malfind_injections.append(line)
for i in xrange(n):
line = next(f, '')
malfind_injections.append(line)
f.close()
return malfind_injections
def analyse_registry(pid):
rhit = False
registry_analysis_matrix = {"\nCollects information about system": registry_infogathering_regex,
"\nQueries / modifies proxy settings": registry_proxy_settings_regex,
"\nReads information about supported languages": registry_locale_regex,
"\nIdentifies machine name": registry_hostname_regex,
"\nIdentifies installed programs": registry_installed_programs_regex,
"\nQueries / modifies remote control settings": registry_remote_control_regex,
"\nQueries / modifies firewall settings": registry_firewall_regex,
"\nQueries / modifies service settings": registry_services_regex,
"\nQueries / modifies network settings": registry_network_regex,
"\nHas access to autorun registry keys": registry_autorun_regex,
"\nQueries / modifies the Windows command processor": registry_command_processor_regex,
"\nQueries / modifies encryption seettings": registry_crypto_regex,
"\nQueries / modifies file association settings": registry_file_associations_regex,
"\nQueries / modifies security seettings": registry_ie_security_regex,
}
for reg_key in registry_analysis_matrix:
registry = anomaly_search("handles", registry_analysis_matrix[reg_key], "yes", "", "diff")
registry_to_report = []
for key in registry:
if re.search(" " + str(pid) + " ", key, re.IGNORECASE) and re.search("Key", key, re.IGNORECASE):
a = re.sub(' +', ' ', key).split(' ')[5:]
b = re.sub('\n', '', ' '.join(a))
registry_to_report.append(b)
if len(registry_to_report) > 0:
if not rhit:
report.write("\n\nInteresting registry handles:")
report.write(
"\n--------------------------------------------------------------------------------------------------------------------------\n")
rhit = True
report_string = ""
registry_to_report = sorted(set(registry_to_report))
for regkey in registry_to_report:
report_string += " " + regkey + "\n"
report.write(reg_key + ":\n" + report_string)
def analyse_imports(pid):
import_analysis_matrix = {"Can create new desktops ": ransomware_imports,
"Can track keyboard strokes ": keylogger_imports,
"Can extract passwords ": password_extract_imports,
"Can access the clipboard ": clipboard_imports,
"Can inject code to other processes ": process_injection_imports,
"Can bypass UAC ": uac_bypass_imports,
"Can use antidebug techniques ": anti_debug_imports,
"Can receive or send files from or to internet ": web_imports,
"Can listen for inbound connections ": listen_imports,
"Can create or start services ": service_imports,
"Can restart or shutdown the system ": shutdown_imports,
"Can interact with the registry ": registry_imports,
"Can create or write to files ": file_imports,
"Can create atoms ": atoms_imports,
"Can identify machine time ": localtime_imports,
"Can interact with or query device drivers ": driver_imports,
"Can enumerate username ": username_imports,
"Can identify machine version information ": machine_version_imports,
"Can query startup information ": startup_imports,
"Can enumerate free disk space ": diskspace_imports,
"Can enumerate system information ": sysinfo_imports
}
impscanfolder = tmpfolder + "impscan/"
hit = False
if os.path.isfile(impscanfolder + str(pid) + ".txt"):
for susp_imports_codename in import_analysis_matrix:
regex = import_analysis_matrix[susp_imports_codename]
susp_functions = []
with open(impscanfolder + str(pid) + ".txt", "r") as imports:
for function in imports:
if re.search(regex, function, re.IGNORECASE):
susp_functions.append(re.sub(' +', ' ', function).split(' ')[3])
if len(susp_functions) > 0:
if not hit:
report.write("\n\nInteresting imports:")
report.write(
"\n--------------------------------------------------------------------------------------------------------------------------\n")
hit = True
report_string = ""
susp_functions = sorted(set(susp_functions))
for function in susp_functions:
if function == susp_functions[-1]:
report_string += re.sub("\n", "", function)
else:
report_string += (re.sub("\n", "", function) + ", ")
report.write(susp_imports_codename + "(" + report_string + ").\n")
def strings(filepath, minimum=4):
with open(filepath, "rb") as f:
result = ""
for c in f.read():
if c in string.printable:
result += c
continue
if len(result) >= minimum:
yield result
result = ""
def analyse_strings(pid):
strings_analysis_matrix = {"IP address(es)": ips_regex,
"Email(s)": emails_regex,
"HTTP URL(s)": domains_regex_http,
"FTP URL(s)": domains_regex_ftp,
"File URL(s)": domains_regex_file,
"Web related keyword(s)": web_regex_str,
"Keylogger keyword(s)": keylogger_regex_str,
"Password keyword(s)": password_regex_str,
"RAT keyword(s)": rat_regex_str,
"Tool(s)": tool_regex_str,
"Banking keyword(s)": banking_regex_str,
"Social website(s)": socialsites_regex_str,
"Antivirus keyword(s)": antivirus_regex_str,
"Anti-sandbox keyword(s)": sandbox_regex_str,
"Virtualisation keyword(s)": virtualisation_regex_str,
"Sysinternal tool(s)": sysinternals_regex_str,
"Powershell keyword(s)": powershell_regex_str,
"SQL keyword(s)": sql_regex_str,
"Shell keyword(s)": shell_regex_str,
"Information gathering keyword(s)": infogathering_regex_str,
"Executable file(s)": exec_regex_str,
"Encryption keyword(s)": crypto_regex_str,
"Filepath(s)": filepath_regex_str,
"Browser keyword(s)": browser_regex_str,
"Misc keyword(s)": other_regex_str
}
dumpfolder = tmpfolder + str(pid) + "/"
filelist = os.listdir(dumpfolder)
hit = False
for susp_strings_codename in strings_analysis_matrix:
regex = strings_analysis_matrix[susp_strings_codename]
susp_strings = []
for f in filelist:
for stringa in strings(dumpfolder + f):
if re.search(regex, stringa, re.IGNORECASE):
for i in re.findall(regex, stringa, re.IGNORECASE):
susp_strings.append(i)
if len(susp_strings) > 0:
if not hit:
report.write("\n\nSuspicious strings from process memory:")
report.write("\n--------------------------------------------------------------------------------------------------------------------------\n")
hit = True
report_string = ""
susp_strings = sorted(set(susp_strings))
for susp_string in susp_strings:
if susp_string == susp_strings[-1]:
report_string += re.sub("\n", "", susp_string)
else:
report_string += (re.sub("\n", "", susp_string) + ", ")
report.write(susp_strings_codename + ": " + report_string + "\n")
def check_expected_parent(pid):
fl = False
expected_parent = ""
childname = get_procname(pid, 'psscan')
parent = ""
for parent in parent_child:
if childname in parent_child[parent]:
fl = True
expected_parent = parent
break
if fl:
actual_parent = get_procname(get_parent_pids_of([pid, ])[0], "psscan")
if actual_parent.lower() != parent.lower():
j = get_associated_process_lines_pids(get_pids(actual_parent))
l = get_associated_process_lines_pids(get_pids(expected_parent))
k = get_associated_process_lines_pids([pid, ])
report_anomalies("Unexpected parent process (" + actual_parent + " instead of " + expected_parent + "):", k + j + l, '-', "psscan", 2)
def get_remote_share_handles(pid):
share_handles_to_report = []
remote_share = anomaly_search("handles", "Device\\\(LanmanRedirector|Mup)", 'yes', '', "diff")
for share_handle in remote_share:
if re.sub(' +', ' ', share_handle).split(' ')[1] == pid:
share_handles_to_report.append(share_handle)
return share_handles_to_report
def get_raw_sockets(pid):
raw_sockets_to_report = []
raw_sockets = anomaly_search("handles", "\\\Device\\\RawIp", 'yes', '', "diff")
for raw_socket in raw_sockets:
if re.sub(' +', ' ', raw_socket).split(' ')[1] == pid:
raw_sockets_to_report.append(raw_socket)
return raw_sockets_to_report
def get_md5(pid):
md5 = ""
dump_folder = tmpfolder + str(pid) + "/"
filelist = os.listdir(dump_folder)
for f in filelist:
if f == "executable." + str(pid) + ".exe":
md5 = hashlib.md5(open(dump_folder + f).read()).hexdigest()
break
return md5
def report_virustotal_md5_results(md5, api):
url = "https://www.virustotal.com/vtapi/v2/file/report"
parameters = {"resource": md5, "apikey": api}
data = urllib.urlencode(parameters)
req = urllib2.Request(url, data)
response_dict = {}
network_error = False
try:
response = urllib2.urlopen(req)
json = response.read()
if json != "":
response_dict = simplejson.loads(json)
except urllib2.URLError:
network_error = True
if not network_error:
report.write("\n\nVirusTotal scan results:")
report.write("\n--------------------------------------------------------------------------------------------------------------------------\n")
report.write("MD5 value: " + md5 + "\n")
if "response_code" in response_dict:
if response_dict["response_code"] == 1:
report.write("VirusTotal scan date: " + str(response_dict["scan_date"]) + "\n")
report.write("VirusTotal engine detections: " + str(response_dict["positives"]) + "/" + str(response_dict["total"]) + "\n")
report.write("Link to VirusTotal report: " + str(response_dict["permalink"]) + "\n")
else:
report.write("Could not find VirusTotal scan results for the MD5 value above.\n")
else:
report.write("VirusTotal request rate limit reached, could not retrieve results.\n")
def main():
# PRINT VOLDIFF BANNER ================================================================
print_voldiff_banner()
global output_dir
global report
global tmpfolder
global profile
global baseline_memory_image
global infected_memory_image
global memory_image
# CHECK THAT VOL.PY IS INSTALLED ================================================================
if not check_volatility_path(path_to_volatility):
print("vol.py does not seem to be installed. Please ensure that volatility is installed/functional before using VolDiff.")
sys.exit()
# READ SYS.ARGV VARIABLES ================================================================
if not len(sys.argv) > 1:
print_help()
elif "--help" in sys.argv:
print_help()
elif "--version" in sys.argv:
print_version()
elif "--dependencies" in sys.argv:
print_dependencies()
if "--output-dir" in sys.argv:
check_enough_arguments_supplied(5)
else:
check_enough_arguments_supplied(3)
if os.path.isfile(sys.argv[2]):
mode = "dual"
if os.path.isfile(sys.argv[1]):
baseline_memory_image = sys.argv[1]
print ("Path to baseline memory image: %s" % baseline_memory_image)
else:
print ("Please specify a valid path to a baseline memory image.")
sys.exit()
infected_memory_image = sys.argv[2]
print ("Path to infected memory image: %s" % infected_memory_image)
if len(sys.argv) == 3:
print ("Profile is not specified. Please specify a profile to use (such as Win7SP1x64).")
sys.exit()
else:
check_profile(sys.argv[3])
profile = sys.argv[3]
else:
mode = "standalone"
if os.path.isfile(sys.argv[1]):
memory_image = sys.argv[1]
print ("Only one memory image specified: standalone mode")
print ("Path to memory image: %s" % memory_image)
else:
print ("Please specify a valid path to a baseline memory image.")
sys.exit()
if len(sys.argv) == 2:
print ("Profile is not specified. Please specify a profile to use (such as Win7SP1x64)!")
sys.exit()
else:
check_profile(sys.argv[2])
profile = sys.argv[2]
# CREATE FOLDER TO STORE OUTPUT ================================================================
starttime = time.time()
output_dir = 'VolDiff_' + datetime.datetime.now().strftime("%d-%m-%Y_%H:%M")
if os.name == 'nt':
output_dir = 'VolDiff_' + datetime.datetime.now().strftime("%d-%m-%Y_%H%M") # can't name file/dir with :
tmpval = False
for arg in sys.argv:
if tmpval:
output_dir = arg
tmpval = False
if arg == "--output-dir":
tmpval = True
tmpfolder = output_dir + '/tmpfolder/'
os.makedirs(tmpfolder)
# RUN VOLATILITY PLUGINS ================================================================
print ("\nRunning a selection of volatility plugins (time consuming):")
sub_procs = {}
file_dict = {}
proc_counter = 0
for plugin in plugins_to_run:
print("Volatility plugin %s execution in progress..." % plugin)
plugin_path = output_dir + '/' + plugin + '/'
os.makedirs(plugin_path)
if plugin == "mutantscan" or plugin == "handles" or plugin == "privs" or plugin == "envars":
option = "--silent"
elif plugin == "threads":
option = "-F OrphanThread"
elif plugin == "psxview":
option = "-R"
elif plugin == "malfind":
if mode == "dual":
dump_dir_baseline = output_dir + '/malfind/dump_dir_baseline/'
os.makedirs(dump_dir_baseline)
option = "--dump-dir=" + output_dir + "/malfind/dump_dir_baseline/"
file_dict[plugin + "baseline"] = open(output_dir + '/' + plugin + '/' + "baseline_" + plugin + ".txt", "w")
sub_procs[plugin + "baseline"] = Popen([path_to_volatility, "--profile", profile, "-f", baseline_memory_image, plugin, option], stdout=file_dict[plugin + "baseline"], stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
dump_dir_infected = output_dir + '/malfind/dump_dir_infected/'
os.makedirs(dump_dir_infected)
option = "--dump-dir=" + output_dir + "/malfind/dump_dir_infected/"
file_dict[plugin + "infected"] = open(output_dir + '/' + plugin + '/' + "infected_" + plugin + ".txt", "w")
sub_procs[plugin + "infected"] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, option], stdout=file_dict[plugin + "infected"], stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
else:
dump_dir = output_dir + '/malfind/dump_dir/'
os.makedirs(dump_dir)
option = "--dump-dir=" + output_dir + "/malfind/dump_dir/"
file_dict[plugin] = open(output_dir + '/' + plugin + '/' + plugin + ".txt", "w")
sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, option], stdout=file_dict[plugin], stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
continue
elif plugin == "procdump":
option = "--dump-dir=" + output_dir + "/procdump/"
if mode == "dual":
sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, "-u", option], stdout=devnull, stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
else:
sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, "-u", option], stdout=devnull, stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
continue
else:
option = ''
# option set, running vol.py processes in //:
if mode == "dual":
file_dict[plugin + "baseline"] = open(output_dir + '/' + plugin + '/' + "baseline_" + plugin + ".txt", "w")
sub_procs[plugin + "baseline"] = Popen([path_to_volatility, "--profile", profile, "-f", baseline_memory_image, plugin, option], stdout=file_dict[plugin + "baseline"], stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
file_dict[plugin + "infected"] = open(output_dir + '/' + plugin + '/' + "infected_" + plugin + ".txt", "w")
sub_procs[plugin + "infected"] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, option], stdout=file_dict[plugin + "infected"], stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
else:
file_dict[plugin] = open(output_dir + '/' + plugin + '/' + plugin + ".txt", "w")
sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, option], stdout=file_dict[plugin], stderr=devnull)
proc_counter += 1
if proc_counter >= max_concurrent_subprocesses:
for pr in sub_procs:
sub_procs[pr].wait()
proc_counter = 0
sub_procs = {}
# ensuring that all subprocesses are completed before proceeding:
for pr in sub_procs:
sub_procs[pr].wait()
for f in file_dict:
file_dict[f].close()
# DEV MODE SWITCH ================================================================
if "--devmode" in sys.argv:
raw_input('\nChange files and hit enter once ready.')
# DIFF OUTPUT RESULTS ================================================================
if mode == "dual":
print ("Diffing output results...")
for plugin in plugins_to_run:
if plugin != "procdump":
diff_files(output_dir + '/' + plugin + '/baseline_' + plugin + ".txt",
output_dir + '/' + plugin + '/infected_' + plugin + ".txt",
output_dir + '/' + plugin + '/diff_' + plugin + ".txt")
if "--no-report" in sys.argv:
script_completion(starttime)
# CREATE REPORT ================================================================
report = open(output_dir + "/VolDiff_Report.txt", 'w')
if mode == "dual":
report.write(" _ ___ _ __ __ \n")
report.write(" /\ /\___ | | / (_)/ _|/ _|\n")
report.write(" \ \ / / _ \| | / /\ / | |_| |_ \n")
report.write(" \ V / (_) | |/ /_//| | _| _|\n")
report.write(" \_/ \___/|_/___,' |_|_| |_| \n")
report.write("\nVolatility analysis report generated by VolDiff v%s" % version)
report.write("\nDownload the latest VolDiff version from https://github.com/aim4r/VolDiff/")
report.write("\n\nBaseline memory image: %s" % baseline_memory_image)
report.write("\nInfected memory image: %s" % infected_memory_image)
report.write("\nProfile: %s" % profile)
report.write("\nDate and time: " + datetime.datetime.now().strftime("%d/%m/%Y %H:%M"))
no_new_entries = []
for plugin in plugins_to_report:
if os.stat(output_dir + "/" + plugin + "/diff_" + plugin + ".txt").st_size == 0:
no_new_entries.append(plugin)
# processing pslist and psscan output:
elif plugin == "pslist" or plugin == "psscan":
# store baseline pids in a list
with open(output_dir + "/" + plugin + "/baseline_" + plugin + ".txt") as baseline:
baseline_pids = []
for line in baseline:
pid = re.sub(' +', ' ', line).split(' ')[2]
baseline_pids.append(pid)
sorted(set(baseline_pids))
# store infected pids in a list
with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as infected:
infected_pids = []
for line in infected:
pid = re.sub(' +', ' ', line).split(' ')[2]
infected_pids.append(pid)
sorted(set(infected_pids))
# get the diff between both
diff_pids = []
for pid in infected_pids:
if pid not in baseline_pids:
diff_pids.append(pid)
# print diff lines
if len(diff_pids) > 0:
report.write("\n\nNew %s entries." % plugin)
report.write(
"\n==========================================================================================================================\n")
with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f:
for i in range(2):
line = next(f, '').strip()
report.write(line + "\n")
for pid in diff_pids:
with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f:
for line in f:
if re.search(r"[a-zA-Z\.]\s+%s " % pid, line, re.IGNORECASE):
report.write(line)
# processing netscan output
elif plugin == "netscan":
report_plugin(plugin, 1)
# filtering mutantscan output
elif plugin == "mutantscan":
with open(output_dir + "/" + plugin + "/diff_" + plugin + ".txt") as diff_mutants:
mutants = []
for line in diff_mutants:
mutant = ' '.join((re.sub(' +', ' ', line).split(' ')[5:]))
if mutant != '\n':
mutants.append(mutant)
mutants = sorted(set(mutants))
if len(mutants) > 0:
report.write("\n\nNew %s entries." % plugin)
report.write(
"\n==========================================================================================================================\n")
for mutant in mutants:
report.write(mutant)
# ensuring malfind output is completely reported
elif plugin == "malfind":
report_plugin(plugin, 0, 500)
# processing plugins that don't need output formatting:
elif plugin == "devicetree" or plugin == "orphanthreads" or plugin == "cmdline" or plugin == "consoles" or plugin == "svcscan" or plugin == "driverirp" or plugin == "shellbags" or plugin == "iehistory" or plugin == "sessions" or plugin == "eventhooks":
report_plugin(plugin)
# processing other plugins:
else:
report_plugin(plugin, 2)
# display list of plugins with no notable changes:
if len(no_new_entries) != 0:
report.write("\n\nNo notable changes to highlight from the following plugins.")
report.write(
"\n==========================================================================================================================\n")
for plugin in no_new_entries:
report.write(plugin + "\n")
# display list of plugins hidden from report (verbose):
report.write("\n\nPlugins that were executed but are not included in the report above.")
report.write(
"\n==========================================================================================================================\n")
report.write(
"filescan\nhandles\ngetsids\ndeskscan\ndlllist\nldrmodules\natoms\nsvcscan\natomscan\nidt\ngdt\ntimers\ngditimers")
# MALWARE CHECKS ================================================================
if "--malware-checks" not in sys.argv:
if mode == "standalone":
try:
os.remove(output_dir + "/VolDiff_Report.txt")
except:
pass
script_completion(starttime)
# PRINT BANNERS ================================================================
print("\nHunting for malicious artifacts in memory...")
if mode == "dual":
report.write("\n\n")
report.write(" _ _ _ __ _ _ \n")
report.write(" /_\ _ __ __ _| |_ _ ___(_)___ /__\ ___ ___ _ _| | |_ ___ \n")
report.write(" //_\\\\| '_ \\ / _\`| | | | / __| / __| / \\/// _ \\/ __| | | | | __/ __|\n")
report.write("/ _ \\ | | | (_| | | |_| \\__ \\ \\__ \\ / _ \\ __/\\__ \\ |_| | | |_\\__ \\\n")
report.write("\_/ \_/_| |_|\__,_|_|\__, |___/_|___/ \/ \_/\___||___/\__,_|_|\__|___/\n")
report.write(" |___/ \n")
elif mode == "standalone":
report.write("\n")
report.write(" _ ___ _ __ __ _ _ _ __ _ _ \n")
report.write(" /\ /\___ | | / (_)/ _|/ _| /_\ _ __ __ _| |_ _ ___(_)___ /__\ ___ ___ _ _| | |_ ___ \n")
report.write(" \\ \\ / / _ \\| | / /\\ / | |_| |_ //_ \\| '_ \\ / _\`| | | | / __| / __| / \\/// _ \\/ __| | | | | __/ __|\n")
report.write(" \\ V / (_) | |/ /_//| | _| _| / _ \\ | | | (_| | | |_| \\__ \\ \\__ \\ / _ \\ __/\\__ \\ |_| | | |_\\__ \\\n")
report.write(" \_/ \___/|_/___,' |_|_| |_| \_/ \_/_| |_|\__,_|_|\__, |___/_|___/ \/ \_/\___||___/\__,_|_|\__|___/\n")
report.write(" |___/ \n")
report.write("\nVolatility analysis report of %s (%s)" % (memory_image, profile))
report.write("\nReport created by VolDiff v" + version + " on the " + datetime.datetime.now().strftime("%d/%m/%Y %H:%M"))
report.write("\nDownload the latest VolDiff version from https://github.com/aim4r/VolDiff/")
# PIDS FOR ANALYSIS ================================================================
pids_to_analyse = {}
if mode == "standalone":
unusual_pids = get_all_pids(usual_processes)
for pid in unusual_pids:
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", non-default process"
else:
pids_to_analyse[pid] = "non-default process"
malfind_pids = get_malfind_pids()
for pid in malfind_pids:
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", potential code injection"
else:
pids_to_analyse[pid] = "potential code injection"
else:
unusual_pids = get_diff_pids("conhost.exe|ipconfig.exe|cmd.exe")
for pid in unusual_pids:
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", new process"
else:
pids_to_analyse[pid] = "New process"
malfind_pids = get_malfind_pids()
for pid in malfind_pids:
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", potential code injection"
else:
pids_to_analyse[pid] = "potential code injection"
# MALWARE CHECKS - NETWORK ================================================================
# compute unique IPs from netscan output:
report_anomalies("IP addresses found in netscan output.", find_ips_domains_emails("netscan"))
# compute unique IPs and domains from iehistory output:
report_anomalies("IP addresses, domains and emails found in iehistory output.", find_ips_domains_emails("iehistory"))
# MALWARE CHECKS - PROCESS ANOMALIES ================================================================
# verify PID of System process = 4
system_pids = get_pids("system")
system_process_check = False
for pid in system_pids:
if pid != '4':
system_process_check = True
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", unusual pid (not 4)"
else:
pids_to_analyse[pid] = "unusual pid (not 4)"
if system_process_check:
l = get_associated_process_lines_pids(system_pids)
report_anomalies("Unusual system process PID (different to 4).", l, "=", "psscan", 2)
# verify that only one instance of certain processes is running:
for process in uniq_processes:
pids = get_pids(process)
if len(pids) > 1:
l = get_associated_process_lines_pids(get_pids(process))
report_anomalies("Unexpected multiple instances of " + process + ".", l, "=", "psscan", 2)
# verify that some processes do not have a child:
nochild_processes = ["lsass.exe", "lsm.exe"]
for process in nochild_processes:
pids = get_pids(process)
childs = get_childs_of(pids)
if len(childs) > 0:
parent_lines = get_associated_process_lines_pids(get_pids(process))
child_lines = get_associated_process_lines_pids(childs)
report_anomalies("Process " + process + " has unexpected childs.", parent_lines + child_lines, "=", "psscan", 2)
for pid in pids:
pidchilds = get_childs_of([pid, ])
if len(pidchilds) > 0:
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", has unexpected child process"
else:
pids_to_analyse[pid] = "has unexpected child process"
# verify child/parent process relationships:
for parent in parent_child:
for child in parent_child[parent]:
child_pids = get_pids(child)
for pid in child_pids:
parent_pids = get_parent_pids_of([pid, ])
parent_procnames = get_procnames(parent_pids)
for parent_procname in parent_procnames:
if parent_procname.lower() != parent.lower():
j = get_associated_process_lines_pids([pid, ])
l = get_associated_process_lines_pids(parent_pids)
report_anomalies("Unexpected parent process of " + child + " PID " + pid + " (" + parent_procname + " instead of " + parent + ").", j + l, "=", "psscan", 2)
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", has an unexpected parent process"
else:
pids_to_analyse[pid] = "has an unexpected parent process"
# verify that every process has a parent (except for explorer.exe, csrss.exe, wininit.exe and winlogon.exe)
pids = get_all_pids()
ppids = get_all_ppids("explorer.exe|csrss.exe|wininit.exe|winlogon.exe|system")
for ppid in ppids:
if ppid not in pids:
l = get_associated_process_lines_ppids([ppid, ])
report_anomalies("Parent process with PPID " + ppid + " is not listed in psscan output.", l, "=", "psscan", 2)
# verify processes are running in expected sessions:
for process in session0_processes:
process_pids = get_pids(process)
for pid in process_pids:
session = get_session(pid)
if session != '0':
l = get_associated_process_lines_pids([pid, ], "pslist")
report_anomalies("Process " + process + " (" + str(pid) + ") is running in unexpected session (" + session + " instead of 0).", l, "=", "pslist", 2)
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", running in an unusual session"
else:
pids_to_analyse[pid] = "running in an unusual session"
for process in session1_processes:
process_pids = get_pids(process)
for pid in process_pids:
session = get_session(pid)
if session != '1':
l = get_associated_process_lines_pids([pid, ], "pslist")
report_anomalies("Process " + process + " (" + str(pid) + ") is running in unexpected session (" + session + " instead of 1).", l, "=", "pslist", 2)
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", running in an unusual session"
else:
pids_to_analyse[pid] = "running in an unusual session"
# check process executable path:
for process in process_execpath:
process_pids = get_pids(process)
for pid in process_pids:
path = get_execpath(pid)
correct_path = process_execpath[process]
if path != "" and path.lower() != correct_path.lower():
l = get_associated_process_lines_pids([pid, ], "psscan")
report_anomalies("Process " + process + " (" + pid + ") is running from an unexpected path (" + path.lower() + " instead of " + correct_path.lower() + ").", l, "=", "psscan", 2)
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", running from an unexpected execution path"
else:
pids_to_analyse[pid] = "running from an unexpected execution path"
# verify if any processes have suspicious l33t names:
leet_processes = anomaly_search("psscan", l33t_process_name, 'yes', '', "diff")
report_anomalies("Suspicious process name found.", leet_processes, "=", "psscan", 2)
for l in leet_processes:
pid = re.sub(' +', ' ', l).split(' ')[2]
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", has a suspicious process name"
else:
pids_to_analyse[pid] = "has a suspicious process name"
# check if any process is running from a TEMP directory:
pid_list = get_all_pids()
for pid in pid_list:
path = get_execpath(pid)
process = get_procname(pid)
if re.search(temp_filepath, path, re.IGNORECASE):
l = get_associated_process_lines_pids([pid, ], "psscan")
report_anomalies("Process " + process + " PID " + pid + " is running from a temporary folder (" + path.lower() + ").", l, "=", "psscan", 2)
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", running from a temporary folder"
else:
pids_to_analyse[pid] = "running from a temporary folder"
# verify if any hacker tools were used in process list:
hacker_processes = anomaly_search("psscan", hacker_process_regex, 'yes', '', "diff")
report_anomalies("Process(es) that may have been used for lateral movement, exfiltration etc.", hacker_processes, "=", "psscan", 2)
# detect process hollowing:
path = output_dir + "/procdump/"
dumped_process_filenames = os.listdir(path)
procnames = get_all_procnames()
for procnameh in procnames:
procpids = get_pids(procnameh)
report_string = ""
if len(procpids) > 1:
procname_sizes = []
unique_procname_sizes = []
for pid in procpids:
for dumped_process_filename in dumped_process_filenames:
if re.search("executable." + pid + ".exe", dumped_process_filename, re.IGNORECASE):
procname_sizes.append(os.stat(path + dumped_process_filename).st_size)
unique_procname_sizes = sorted(set(procname_sizes))
report_string += procnameh + " " + pid + " " + str(
os.stat(path + dumped_process_filename).st_size) + "\n"
if len(unique_procname_sizes) > 1:
report.write("\n\nPotential process hollowing detected in " + procnameh + " (based on size).")
report.write(
"\n==========================================================================================================================\n")
report.write("\nProcess PID Size")
report.write("\n----------------------------\n")
report.write(report_string)
# detect processes with exit time but active threads:
report_anomalies("Process(es) with exit time and active threads.", deadproc_activethreads(), "=", "psxview", 2)
for d in deadproc_activethreads():
pid = re.sub(' +', ' ', d).split(' ')[2]
if pid in pids_to_analyse:
pids_to_analyse[pid] += ", has an exit time and active threads"
else:
pids_to_analyse[pid] = "has an exit time and active threads"
# check if any process has domain or enterprise admin privileges:
high_privileges_regex = "Domain Admin|Enterprise Admin|Schema Admin"
high_privileges = anomaly_search("getsids", high_privileges_regex, 'yes', '', "diff")
report_anomalies("Process(es) with domain or enterprise admin privileges.", high_privileges)
# check if any process has debug privileges:
debug_privileges = anomaly_search("getsids", "debug", 'yes', '', "diff")
report_anomalies("Process(es) with debug privileges.", debug_privileges)
# MALWARE CHECKS - SUSPICIOUS DLLs/EXEs ================================================================
# Prefetch artifacts (mftparser): [DUAL ONLY]
if mode == "dual":
prefetch_files = anomaly_search("mftparser", ".pf$", 'yes')
prefetch_files_to_report = []
for entry in prefetch_files:
pf = ' '.join((re.sub(' +', ' ', entry).split(' ')[12:]))
if pf != "":
prefetch_files_to_report.append(pf)
report_anomalies("Prefetch artifacts (mftparser).", prefetch_files_to_report)
# Suspicious dlls/executables (dlllist) - loaded from temp folders, unusual new (DUAL ONLY), etc:
temp_dlls = anomaly_search("dlllist", temp_filepath, 'yes')
temp_dlls = extract_substrings(temp_dlls, temp_filepath)
new_exe_excluded_regex = "system32|explorer.exe|iexplore.exe|VMware|wininit.exe|winlogon.exe|TrustedInstaller.exe|taskhost.exe|mscorsvw.exe|TPAutoConnect.exe|comctl32.dll"
new_exes = anomaly_search("dlllist", "Command line", 'yes', new_exe_excluded_regex)
if mode == "dual":
new_dlls = anomaly_search("dlllist", "C:.*.dll", 'yes', "System32")
new_dlls = extract_substrings(new_dlls, "C:.*.dll")
dlls = temp_dlls + new_exes + new_dlls
else:
dlls = temp_dlls + new_exes
dlls_to_report = []
for dll in dlls:
b = re.sub('"', '', dll)
c = re.sub("Command line : ", "", b)
dlls_to_report.append(c)
dlls_to_report = tidy_list(dlls_to_report)
report_anomalies("Suspicious DLLs/EXEs (dlllist).", dlls_to_report)
# Hidden/suspicious DLLs/EXEs (ldrmodules):
ldrmodules_excluded_regex_1 = "System32\\\msxml6r.dll|System32\\\oleaccrc.dll|System32\\\imageres.dll|System32\\\\ntdll.dll|System32\\\winlogon.exe|System32\\\services.exe|System32\\\tquery.dll|System32\\\wevtapi.dll"
hiddendlls1_ldrmodules = anomaly_search("ldrmodules", "False False False.*dll$|False False False.*exe$", 'yes', ldrmodules_excluded_regex_1)
ldrmodules_excluded_regex_2 = "system32|explorer.exe|iexplore.exe|.fon$|TrustedInstaller.exe|VMware\\\VMware Tools|mscorsvw.exe"
hiddendlls2_ldrmodules = anomaly_search("ldrmodules", "False", 'yes', ldrmodules_excluded_regex_2)
hiddendlls3_ldrmodules = anomaly_search("ldrmodules", "no name", 'yes')
hiddendlls_ldrmodules = hiddendlls1_ldrmodules + hiddendlls2_ldrmodules + hiddendlls3_ldrmodules
hiddendlls_ldrmodules = sorted(set(hiddendlls_ldrmodules))
report_anomalies("Hidden/suspicious DLLs/EXEs (ldrmodules).", hiddendlls_ldrmodules, "=", "ldrmodules", 2)
# Suspicious DLLs (atoms):
dll_atoms = anomaly_search("atoms", ".dll$", 'yes', usual_atoms_dlls)
report_anomalies("Suspicious DLLs (atoms).", dll_atoms, "=", "atoms", 2)
# Suspicious DLLs (atomscan):
dll_atomscan = anomaly_search("atomscan", ".dll$", 'yes', usual_atoms_dlls)
report_anomalies("Suspicious DLLs (atomscan).", dll_atomscan, "=", "atomscan", 2)
# DLLs used for password theft or VM evasion (ldrmodules):
suspdll_ldrmodules = anomaly_search("ldrmodules", hacker_dll_regex, 'yes')
report_anomalies("DLLs used for password theft or VM evasion (ldrmodules).", suspdll_ldrmodules, "=", "ldrmodules", 2)
# MALWARE CHECKS - SUSPICIOUS FILES ================================================================
# Interesting files on disk (filescan)
if mode == "dual":
suspicious_files1 = anomaly_search("filescan", susp_filepath, 'yes', "\.db$|\.lnk$|\.ini$|\.log$", "diff")
suspicious_files2 = anomaly_search("filescan", susp_extensions_regex, 'yes', "suspend-vm-default\.bat")
suspicious_files = suspicious_files1 + suspicious_files2
else:
suspicious_files = anomaly_search("filescan", susp_extensions_regex, 'yes')
suspicious_files_to_report = []
for entry in suspicious_files:
en = ' '.join((re.sub(' +', ' ', entry).split(' ')[4:]))
if en != "":
suspicious_files_to_report.append(en)
suspicious_files_to_report = sorted(set(suspicious_files_to_report))
report_anomalies("Interesting files on disk (filescan).", suspicious_files_to_report, "=", "filescan", 0, 100)
# Alternate Data Stream (ADS) files (mftparser):
ads_files = anomaly_search("mftparser", "DATA ADS", 'yes', "Bad$|Max$")
report_anomalies("Alternate Data Stream (ADS) files (mftparser).", ads_files)
# MALWARE CHECKS - MISC ================================================================
# find suspicious desktop instances: [DUAL ONLY]
if mode == "dual":
new_desktops = anomaly_search("deskscan", "Desktop:", 'yes', '', 'diff')
report_anomalies("New desktop instances (deskscan).", new_desktops)
# find interesting entries in hosts file
if mode == "dual":
hostsb = get_hosts_contents(baseline_memory_image)
hostsi = get_hosts_contents(infected_memory_image)
hosts = []
for line in hostsi:
if line not in hostsb:
hosts.append(line)
else:
hosts = get_hosts_contents(memory_image)
report_anomalies("Interesting 'hosts' file entries.", hosts)
# MALWARE CHECKS - PERSISTENCE ================================================================
# find new services: [Dual Only]
if mode == "dual":
services_to_report = filter_new_services()
report_anomalies("Notable new entries from svcscan.", services_to_report)
# highlight temp folders appearing in services: [Standalone Only]
if mode == "standalone":
temp_services = anomaly_search("svcscan", temp_filepath, 'yes')
report_anomalies("Temp folders appearing in svcscan output.", temp_services)
# MALWARE CHECKS - KERNEL ================================================================
# Keylogger traces (messagehooks):
keylogger_messagehooks = anomaly_search("messagehooks", "KEYBOARD", 'yes')
report_anomalies("Keylogger traces (messagehooks).", keylogger_messagehooks, "=", "messagehooks", 2)
# Unusual timers:
unusual_timers = anomaly_search_inverted("timers", usual_timers, 'yes')
if mode == "standalone":
report_anomalies("Unusual timers.", unusual_timers[2:], "=", "timers", 2)
else:
report_anomalies("Unusual timers.", unusual_timers, "=", "timers", 2)
# Suspicious 'unknown' timers:
unknown_timers = anomaly_search("timers", "UNKNOWN", 'yes')
report_anomalies("Suspicious 'unknown' timers.", unknown_timers, "=", "timers", 2)
# find unusual gditimers:
unusual_gditimers = anomaly_search_inverted("gditimers", usual_gditimers, 'yes')
if mode == "standalone":
report_anomalies("Unusual gditimers.", unusual_gditimers[2:], "=", "gditimers", 2, 20)
else:
report_anomalies("Unusual gditimers.", unusual_gditimers, "=", "gditimers", 2, 20)
# Suspicious 'unknown' callbacks:
unknown_callbacks = anomaly_search("callbacks", "UNKNOWN", 'yes')
report_anomalies("Suspicious 'unknown' callbacks.", unknown_callbacks, "=", "callbacks", 2)
# Suspicious 'unknown' drivermodules:
unknown_drivermodules = anomaly_search("drivermodule", "UNKNOWN", 'yes')
report_anomalies("Suspicious 'unknown' drivermodules.", unknown_drivermodules, "=", "drivermodule", 2, 20)
# Suspicious 'unknown' driverirp entries:
unknown_driverirp = anomaly_search("driverirp", "UNKNOWN", 'yes')
report_anomalies("Suspicious 'unknown' driverirp entries.", unknown_driverirp, "=", "", 0, 20)
# Unusual ssdt entries:
unusual_ssdt = anomaly_search_inverted("ssdt", usual_ssdt, 'yes', "Entry")
report_anomalies("Unusual ssdt entries.", unusual_ssdt, "=", "", 0, 20)
# Suspicious idt entries:
susp_idt = anomaly_search("idt", "rsrc", 'yes')
report_anomalies("Suspicious idt entries.", susp_idt, "=", "idt", 2)
# Suspicious orphan threads:
orphan_threads = anomaly_search("threads", ".*", 'yes')
if mode == "standalone":
report_anomalies("Suspicious orphan threads.", orphan_threads[2:])
else:
report_anomalies("Suspicious orphan threads.", orphan_threads)
# IMPSCAN AND PROCDUMP EXECUTION (IN PREPERATION FOR PROCESS PROFILER) ================================================================
# run impscan plugin in //
impscanfolder = tmpfolder + "impscan/"
if not os.path.isdir(impscanfolder):
os.makedirs(impscanfolder)
else:
shutil.rmtree(impscanfolder)
os.makedirs(impscanfolder)
plugin = "impscan"
i = 0
subprocesses = {}
for pid in pids_to_analyse:
if mode == "dual":
with open(impscanfolder + str(pid) + ".txt", "w") as f:
subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, "--pid=" + str(pid)], stdout=f, stderr=devnull)
else:
with open(impscanfolder + str(pid) + ".txt", "w") as f:
subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, "--pid=" + str(pid)], stdout=f, stderr=devnull)
i += 1
if i >= max_concurrent_subprocesses:
for subproc in subprocesses:
subprocesses[subproc].wait()
i = 0
subprocesses = {}
for subproc in subprocesses:
subprocesses[subproc].wait()
# dump suspicious processes to disk in //
subprocesses = {}
i = 0
for pid in pids_to_analyse:
procname = get_procname(pid)
dumpfolder = tmpfolder + str(pid) + "/"
if not os.path.isdir(dumpfolder):
os.makedirs(dumpfolder)
else:
shutil.rmtree(dumpfolder)
os.makedirs(dumpfolder)
offsets = []
if procname != "":
f = open_full_plugin("psscan", 2)
for line in f:
if re.search(procname + " +" + str(pid) + " ", line, re.IGNORECASE):
offsets.append(re.sub(' +', ' ', line).split(' ')[0])
f.close()
for offset in offsets:
if mode == "dual":
subprocesses[offset] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, "procdump", "--offset=" + offset, "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull)
else:
subprocesses[offset] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, "procdump", "--offset=" + offset, "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull)
i += 1
if i >= max_concurrent_subprocesses:
for subproc in subprocesses:
subprocesses[subproc].wait()
i = 0
subprocesses = {}
if mode == "dual":
subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, "malfind", "--pid=" + str(pid), "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull)
else:
subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, "malfind", "--pid=" + str(pid), "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull)
i += 1
if i >= max_concurrent_subprocesses:
for subproc in subprocesses:
subprocesses[subproc].wait()
i = 0
subprocesses = {}
for subproc in subprocesses:
subprocesses[subproc].wait()
# DEV MODE SWITCH ================================================================
if "--devmode" in sys.argv:
raw_input('\nCheckpoint before executing process profiler.')
# MALWARE CHECKS - PROCESS PROFILER ================================================================
# dispay list of processes that will be analysed
if len(pids_to_analyse) > 0:
report.write("\n\nProcesses that will be analysed in the next section:")
report.write(
"\n==========================================================================================================================\n")
for pid in pids_to_analyse:
report.write(get_procname(pid) + " (" + str(pid) + "): " + pids_to_analyse[pid] + ".\n")
l = get_associated_process_lines_pids(pids_to_analyse, "psscan")
report_anomalies("Psscan output for suspicious processes.", l, "-", "psscan", 2)
for pid in pids_to_analyse:
procname = get_procname(pid)
report.write("\n\nAnalysis results for " + procname + " PID " + pid + " (" + pids_to_analyse[pid] + "):")
report.write(
"\n==========================================================================================================================")
# print VirusTotal scan results of exec MD5 hash
if vt_api_key != "":
susp_md5 = get_md5(pid)
if susp_md5 != "":
report_virustotal_md5_results(susp_md5, vt_api_key)
# print psxview output for the process (psxview)
l = get_associated_process_lines_pids([pid, ], "psxview")
report_anomalies("Psxview results:", l, '-', "psxview", 2)
# print comand line (cmdline)
cmdline = get_cmdline(pid)
report_anomalies("Command line (cmdline):", cmdline, '-')
# analyse network connections (netscan)
susp_connections = anomaly_search("netscan", " " + str(pid), "yes", "", "diff")
report_anomalies("Network connections (netscan):", susp_connections, '-', "netscan", 1)
# print parent process information
if len(get_parent_pids_of([pid, ])) > 0:
ppid = get_parent_pids_of([pid, ])[0]
pids = get_all_pids()
if ppid not in pids:
l = get_associated_process_lines_ppids([ppid, ])
report_anomalies("Parent process (PPID " + ppid + ") is not listed in psscan output:", l, '-', "psscan",
2)
else:
l = get_associated_process_lines_pids([ppid, ])
j = get_associated_process_lines_pids([pid, ])
report_anomalies("Parent process (PPID " + ppid + ") information:", j + l, '-', "psscan", 2)
# check if process has an "expected" parent
check_expected_parent(pid)
# print child process information
childs = get_childs_of([pid, ])
l = get_associated_process_lines_pids(childs)
report_anomalies("Child process(es):", l, '-', "psscan", 2)
# print malfind injections (malfind)
malfind_injections = get_malfind_injections(pid, mode)
report_anomalies("Code injection (malfind):", malfind_injections, '-')
# print associated services (svcscan)
susp_services = get_associated_services(pid)
report_anomalies("Associated service(s) (svcscan):", susp_services, '-')
# print envars (envars)
associated_envars = anomaly_search("envars", " " + str(pid) + " ", "yes", "", "diff")
report_anomalies("Environment variables (envars):", associated_envars, '-', "envars", 2)
# print interesting DLLs (ldrmodules)
dlls1 = anomaly_search("ldrmodules", hacker_dll_regex, 'yes', "", "diff")
dlls2 = anomaly_search("ldrmodules", "no name", 'yes', "", "diff")
dlls3 = anomaly_search("ldrmodules", "False False False.*dll$|False False False.*exe$", 'yes', ldrmodules_excluded_regex_1, "diff")
dlls4 = anomaly_search("ldrmodules", "False", 'yes', ldrmodules_excluded_regex_2, "diff")
dlls = sorted(set(dlls1 + dlls2 + dlls3 + dlls4))
dlls_to_report = []
for dll in dlls:
if re.search(" " + str(pid) + " ", dll, re.IGNORECASE):
dlls_to_report.append(dll)
report_anomalies("Interesting DLLs (ldrmodules):", dlls_to_report, '-', "ldrmodules", 2)
# print mutants (handles) DUAL ONLY
if mode == "dual":
mutants = anomaly_search("handles", " " + str(pid) + " .*Mutant", "yes", "", "diff")
report_anomalies("Mutants accessed (handles):", mutants, '-', "handles", 2)
# print interesting files accessed (handles)
files1 = anomaly_search("handles", " " + str(pid) + " .*\\\Device\\\RawIp", 'yes', '', "diff")
files2 = anomaly_search("handles", " " + str(pid) + " .*Device\\\(LanmanRedirector|Mup)", 'yes', '', "diff")
files3 = anomaly_search("handles", " " + str(pid) + " .*\..{2,3}$", 'yes', "\.mui$", "diff")
files_to_report = []
filelist = sorted(set(files1 + files2 + files3))
for i in filelist:
if re.search("File", i, re.IGNORECASE):
files_to_report.append(i)
report_anomalies("Interesting files accessed (handles):", files_to_report, '-', "handles", 2)
# print privileges (privs)
privs = anomaly_search("privs", " " + str(pid) + " ", "yes", "", "diff")
report_anomalies("Enabled privileges (privs):", privs, '-', "privs", 2)
# print high privileges (getsids)
sids = anomaly_search("getsids", "\(" + str(pid) + "\).*(Domain Admin|Enterprise Admin|Schema Admin)", "yes", "", "diff")
report_anomalies("Process privileges (getsids):", sids, '-')
# check if process has a raw socket handle:
raw_sockets = get_raw_sockets(pid)
report_anomalies("Raw socket handles:", raw_sockets, "-", "handles", 2)
# check if process has a handle to a remote mapped share:
share_handles = get_remote_share_handles(pid)
report_anomalies("Remote share handles:", share_handles, "-", "handles", 2)
# print handles to interesting registry entries (handles)
if get_procname(pid) != 'explorer.exe':
analyse_registry(pid)
# print interesting imports (impscan)
if get_procname(pid) != 'explorer.exe':
analyse_imports(pid)
# find suspicious strings in process strings (procdump + malfind + strings)
analyse_strings(pid)
# CLEANUP AND CLOSURE ================================================================
script_completion(starttime)
if __name__ == '__main__':
main()
gitextract__n1rca3z/ ├── LICENSE ├── README.md └── VolDiff.py
SYMBOL INDEX (50 symbols across 1 files) FILE: VolDiff.py function print_voldiff_banner (line 174) | def print_voldiff_banner(): function print_help (line 184) | def print_help(): function print_version (line 198) | def print_version(): function print_dependencies (line 206) | def print_dependencies(): function check_volatility_path (line 212) | def check_volatility_path(path): function check_enough_arguments_supplied (line 223) | def check_enough_arguments_supplied(n=4): function check_profile (line 230) | def check_profile(pr): function script_completion (line 243) | def script_completion(start_time): function diff_files (line 260) | def diff_files(path1, path2, diffpath): function report_plugin (line 277) | def report_plugin(plugin, header_lines=0, threshold=diff_output_threshold): function open_report (line 298) | def open_report(report_path): function notify_completion (line 311) | def notify_completion(message): function open_full_plugin (line 318) | def open_full_plugin(plugin="psscan", lines_to_ignore=2, state="infected"): function open_diff_plugin (line 328) | def open_diff_plugin(plugin="psscan", lines_to_ignore=2): function anomaly_search (line 338) | def anomaly_search(plugin, regex_to_include, ignorecase='yes', regex_to_... function anomaly_search_inverted (line 361) | def anomaly_search_inverted(plugin, regex_to_exclude, ignorecase='yes', ... function report_anomalies (line 381) | def report_anomalies(headline, anomaly_list, delim="=", plugin="", heade... function extract_substrings (line 411) | def extract_substrings(input_list, regex): function tidy_list (line 421) | def tidy_list(input_list): function find_ips_domains_emails (line 431) | def find_ips_domains_emails(plugin): function get_pids (line 462) | def get_pids(procname, plugin="psscan"): function get_associated_process_lines_pids (line 475) | def get_associated_process_lines_pids(pids, plugin="psscan"): function get_associated_process_lines_ppids (line 486) | def get_associated_process_lines_ppids(ppids, plugin="psscan"): function get_childs_of (line 497) | def get_childs_of(pids): function get_parent_pids_of (line 510) | def get_parent_pids_of(childs): function get_procnames (line 522) | def get_procnames(pids): function get_all_pids (line 533) | def get_all_pids(exception_regex=''): function get_diff_pids (line 548) | def get_diff_pids(exception_regex=''): function get_procname (line 563) | def get_procname(pid, plugin='psscan'): function get_all_procnames (line 575) | def get_all_procnames(plugin='psscan', exception_regex=''): function get_all_ppids (line 588) | def get_all_ppids(exception_regex=''): function get_session (line 601) | def get_session(pid): function get_execpath (line 612) | def get_execpath(pid): function get_cmdline (line 627) | def get_cmdline(pid): function deadproc_activethreads (line 644) | def deadproc_activethreads(): function get_hosts_contents (line 654) | def get_hosts_contents(memory_image_file): function filter_new_services (line 679) | def filter_new_services(): function get_associated_services (line 693) | def get_associated_services(pid): function get_malfind_pids (line 707) | def get_malfind_pids(): function get_malfind_injections (line 718) | def get_malfind_injections(pid, m="dual"): function analyse_registry (line 736) | def analyse_registry(pid): function analyse_imports (line 774) | def analyse_imports(pid): function strings (line 823) | def strings(filepath, minimum=4): function analyse_strings (line 835) | def analyse_strings(pid): function check_expected_parent (line 888) | def check_expected_parent(pid): function get_remote_share_handles (line 907) | def get_remote_share_handles(pid): function get_raw_sockets (line 916) | def get_raw_sockets(pid): function get_md5 (line 925) | def get_md5(pid): function report_virustotal_md5_results (line 936) | def report_virustotal_md5_results(md5, api): function main (line 965) | def main():
Condensed preview — 3 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (99K chars).
[
{
"path": "LICENSE",
"chars": 1290,
"preview": "Copyright (c) 2016, @aim4r\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodi"
},
{
"path": "README.md",
"chars": 1888,
"preview": "\nVolDiff: Malware Memory Footprint Analysis\n==========================================\n\nVolDiff is a Python script that "
},
{
"path": "VolDiff.py",
"chars": 91486,
"preview": "#!/usr/bin/env python\n# VolDiff malware analysis script by @aim4r\n__author__ = 'aim4r'\n\n# IMPORTS ======================"
}
]
About this extraction
This page contains the full source code of the aim4r/VolDiff GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 3 files (92.4 KB), approximately 22.9k tokens, and a symbol index with 50 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.