├── LICENSE ├── README.md └── VolDiff.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, @aim4r 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | VolDiff: Malware Memory Footprint Analysis 3 | ========================================== 4 | 5 | VolDiff is a Python script that leverages the [Volatility](https://github.com/volatilityfoundation/volatility) framework to identify malware threats on Windows 7 memory images. 6 | 7 | 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. 8 | 9 | VolDiff can also be used against a single Windows memory image to automate Volatility plugin execution, and hunt for malicious patterns. 10 | 11 | Installation and use directions 12 | -------------------------------- 13 | 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. 14 | 15 | Sample report 16 | -------------- 17 | 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. 18 | 19 | 20 | Inspiration 21 | ------------ 22 | 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! 23 | -------------------------------------------------------------------------------- /VolDiff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # VolDiff malware analysis script by @aim4r 3 | __author__ = 'aim4r' 4 | 5 | # IMPORTS ================================================================ 6 | import os 7 | import sys 8 | import time 9 | import datetime 10 | import difflib 11 | import shutil 12 | import re 13 | import string 14 | import simplejson 15 | import urllib 16 | import urllib2 17 | import hashlib 18 | from subprocess import Popen 19 | 20 | # VARIABLES ================================================================ 21 | version = "2.1.5" 22 | path_to_volatility = "vol.py" 23 | max_concurrent_subprocesses = 3 24 | diff_output_threshold = 100 25 | ma_output_threshold = 60 26 | vt_api_key = "473db868008cddb184e609ace3ca05de8e51f806e57eba74005aba2efa4a1e6e" # the rate limit os 4 requests per IP per minute 27 | devnull = open(os.devnull, 'w') 28 | 29 | # volatility plugins to run: 30 | plugins_to_run = ["handles", "psxview", "netscan", "iehistory", "getsids", "pslist", "psscan", "cmdline", "consoles", 31 | "dlllist", "filescan", "shimcache", "shellbags", "sessions", "messagehooks", "eventhooks", "svcscan", 32 | "envars", "mutantscan", "symlinkscan", "atoms", "atomscan", "drivermodule", "mftparser", "driverscan", 33 | "devicetree", "modules", "modscan", "unloadedmodules", "callbacks", "ldrmodules", "privs", "hashdump", 34 | "threads", "malfind", "procdump", "idt", "gdt", "driverirp", "deskscan", "timers", "gditimers", 35 | "ssdt"] 36 | 37 | # volatility plugins to report / only used when a baseline memory image is provided: 38 | plugins_to_report = ["pslist", "psscan", "psxview", "netscan", "iehistory", "malfind", "sessions", "privs", 39 | "messagehooks", "eventhooks", "envars", "shimcache", "shellbags", "cmdline", "consoles", 40 | "hashdump", "drivermodule", "driverscan", "driverirp", "modules", "modscan", "unloadedmodules", 41 | "devicetree", "callbacks", "threads", "mutantscan", "symlinkscan", "ssdt"] 42 | 43 | # REGEX EXPRESSIONS ================================================================ 44 | # regex expressions used to analyse imports 45 | ransomware_imports = "CreateDesktop" 46 | keylogger_imports = "GetKeyboardState|GetKeyState" 47 | password_extract_imports = "SamLookupDomainInSamServer|NlpGetPrimaryCredential|LsaEnumerateLogonSessions|SamOpenDomain|SamOpenUser|SamGetPrivateData|SamConnect|SamRidToSid|PowerCreateRequest|SeDebugPrivilege|SystemFunction006|SystemFunction040" 48 | clipboard_imports = "OpenClipboard" 49 | process_injection_imports = "VirtualAllocEx|AllocateVirtualMemory|VirtualProtectEx|ProtectVirtualMemory|CreateProcess|LoadLibrary|LdrLoadDll|CreateToolhelp32Snapshot|QuerySystemInformation|EnumProcesses|WriteProcessMemory|WriteVirtualMemory|CreateRemoteThread|ResumeThread|SetThreadContext|SetContextThread|QueueUserAPC|QueueApcThread|WinExec|FindResource" 50 | uac_bypass_imports = "AllocateAndInitializeSid|EqualSid|RtlQueryElevationFlags|GetTokenInformation|GetSidSubAuthority|GetSidSubAuthorityCount" 51 | anti_debug_imports = "SetUnhandledExceptionFilter|CheckRemoteDebugger|DebugActiveProcess|FindWindow|GetLastError|GetWindowThreadProcessId|IsDebugged|IsDebuggerPresent|NtCreateThreadEx|NtGlobalFlags|NtSetInformationThread|OutputDebugString|pbIsPresent|Process32First|Process32Next|TerminateProcess|ThreadHideFromDebugger|UnhandledExceptionFilter|ZwQueryInformation|Sleep|GetProcessHeap" 52 | web_imports = "InternetReadFile|recvfrom|WSARecv|DeleteUrlCacheEntry|CreateUrlCacheEntry|URLDownloadToFile|WSASocket|WSASend|WSARecv|WS2_32|InternetOpen|HTTPOpen|HTTPSend|InternetWrite|InternetConnect" 53 | listen_imports = "RasPortListen|RpcServerListen|RpcMgmtWaitServerListen|RpcMgmtIsServerListening" 54 | service_imports = "OpenService|CreateService|StartService|NdrClientCall2|NtLoadDriver" 55 | shutdown_imports = "ExitWindows" 56 | registry_imports = "RegOpenKey|RegQueryValue|ZwSetValueKey" 57 | file_imports = "CreateFile|WriteFile" 58 | atoms_imports = "GlobalAddAtom" 59 | localtime_imports = "GetLocalTime|GetSystemTime" 60 | driver_imports = "DeviceIoControl" 61 | username_imports = "GetUserName|LookupAccountNameLocal" 62 | machine_version_imports = "GetVersion" 63 | startup_imports = "GetStartupInfo" 64 | diskspace_imports = "GetDiskFreeSpace" 65 | sysinfo_imports = "CreateToolhelp32Snapshot|NtSetSystemInformation|NtQuerySystemInformation|GetCurrentProcess|GetModuleFileName" 66 | 67 | # regex expressions used to analyse strings (from process executables) 68 | web_regex_str = "cookie|download|proxy|responsetext|socket|useragent|user-agent|urlmon|user_agent|WebClient|winhttp|http" 69 | antivirus_regex_str = "antivir|anvir|avast|avcons|avgctrl|avginternet|avira|bitdefender|checkpoint|comodo|F-Secure|firewall|kaspersky|mcafee|norton|norman|safeweb|sophos|symantec|windefend" 70 | virtualisation_regex_str = "000569|001C14|080027|citrix|parallels|proxmox|qemu|SbieDll|Vbox|VMXh|virm|virtualbox|virtualpc|vmsrvc|vpc|winice|vmware|xen" 71 | 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" 72 | sysinternals_regex_str = "filemon|sysinternal|procdump|procexp|procmon|psexec|regmon|sysmon" 73 | shell_regex_str = "shellexecute|shell32" 74 | keylogger_regex_str = "backspace|klog|keylog|shift" 75 | filepath_regex_str = 'C:\\\(?:[^\\\/:*?"<>|\r\n]+\\\)*[^\\\/:*?"<>|\r\n]*' 76 | password_regex_str = "brute|credential|creds|mimikatz|passwd|password|pwd|sniff|stdapi|WCEServicePipe|wce_krbtkts" 77 | powershell_regex_str = "powerview|powershell" 78 | sql_regex_str = "SELECT|INSERT|sqlite|MySQL" 79 | infogathering_regex_str = "driverquery|gethost|wmic|GetVolumeInformation|systeminfo|tasklist|reg.exe" 80 | tool_regex_str = "cain|clearev|ipscan|netsh|rundll32|timestomp|torrent" 81 | banking_regex_str = "banc|banco|bank|Barclays|hsbc|jpmorgan|lloyds|natwest|nwolb|paypal|rbc.com|santander" 82 | socialsites_regex_str = "facebook|instagram|linkedin|pastebin|twitter|yahoo|youtube" 83 | exec_regex_str = ".*\.bat|.*\.cmd|.*\.class|.*\.exe|.*\.jar|.*\.js|.*\.jse|.*\.SCR|.*\.VBE|.*\.vbs" 84 | crypto_regex_str = "bitlocker|bitcoin|CIPHER|crypt|locker|logkey|publickey|ransom|truecrypt|veracrypt" 85 | rat_regex_str = "backdoor|botnet|login|malware|rootkit|screenshot|Trojan|Vnc|VncStart" 86 | browser_regex_str = "chrome|firefox|mozilla|opera" 87 | other_regex_str = "admin|currentversion|hosts|registry|smtp|UserInit|.*\.pdb" 88 | 89 | # regex expressions used to extract ips, domains and email addresses 90 | 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" 91 | domains_regex_http = 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+' 92 | domains_regex_ftp = 'ftp[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+' 93 | domains_regex_file = 'file[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+' 94 | emails_regex = r"\b[a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9.-]+\b" 95 | 96 | # regex expressions used to analyse registry handles 97 | registry_infogathering_regex = "SOFTWARE\\\MICROSOFT|Parameters|SOFTWARE\\\POLICIES" 98 | registry_proxy_settings_regex = "INTERNET SETTINGS" 99 | registry_locale_regex = "NLS\\\LOCALE" 100 | registry_hostname_regex = "COMPUTERNAME" 101 | registry_installed_programs_regex = "CurrentVersion\\\App Paths|CurrentVersion\\\Uninstall|Installed Components" 102 | registry_remote_control_regex = "Terminal Server|Realvnc" 103 | registry_firewall_regex = "firewall" 104 | registry_services_regex = "CurrentControlSet\\\services" 105 | registry_network_regex = "NetworkList|Tcpip" 106 | registry_autorun_regex = "CurrentVersion\\\Explorer|CurrentVersion\\\Run|CurrentVersion\\\Windows|Current Version\\\Policies\\\Explorer|CurrentVersion\\\Winlogon|Shell Extensions" 107 | registry_command_processor_regex = "Command Processor" 108 | registry_crypto_regex = "CRYPTOGRAPHY" 109 | registry_tracing_regex = "TRACING" 110 | registry_file_associations_regex = "SYSTEMFILEASSOCIATIONS" 111 | registry_ie_security_regex = "INTERNET EXPLORER\\\SECURITY" 112 | registry_security_center_regex = "Security Center" 113 | 114 | # suspicious processes and dlls 115 | 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" 116 | hacker_dll_regex = "mimilib.dll|sekurlsa.dll|wceaux.dll|iamdll.dll|VMCheck.dll" 117 | # suspicious process names 118 | 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" 119 | # "usual" process names 120 | 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" 121 | # suspicious filepaths 122 | susp_filepath = "\\\ProgramData|\\\Recycle|\\\Windows\\\Temp|\\\Users\\\All|\\\Users\\\Default|\\\Users\\\Public|\\\ProgramData|AppData" 123 | temp_filepath = "\\\TMP|\\\TEMP|\\\AppData" 124 | # usual timers 125 | 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" 126 | # usual gditimers 127 | usual_gditimers = "dllhost.exe|explorer.exe|csrss.exe" 128 | # usual ssdt 129 | usual_ssdt = "(ntos|win32k)" 130 | # usual atoms and atomscan dlls 131 | 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" 132 | # extensions of interest 133 | susp_extensions_regex = "\.job$|\.pdb$|\.xls$|\.doc$|\.pdf$|\.tmp$|\.temp$|\.rar$|\.zip$|\.bat|\.cmd$|\.class$|\.jar$|\.jse$|\.SCR$|\.VBE$|\.vbs$" 134 | 135 | # DICTIONARIES/LISTS USED FOR PROCESS CHECKS ================================================================ 136 | # list of "unique" processes 137 | uniq_processes = ["services.exe", "System", "wininit.exe", "smss.exe", "lsass.exe", "lsm.exe", "explorer.exe"] 138 | # expected execution path for some processes 139 | process_execpath = {'smss.exe': "\systemroot\system32\smss.exe", 140 | "crss.exe": "\windows\system32\csrss.exe", 141 | "wininit.exe": "wininit.exe", 142 | "services.exe": "\windows\system32\services.exe", 143 | "lsass.exe": "\windows\system32\lsass.exe", 144 | "svchost.exe": "\windows\system32\svchost.exe", 145 | "lsm.exe": "\windows\system32\lsm.exe", 146 | "explorer.exe": "\windows\explorer.exe", 147 | "winlogon.exe": "winlogon.exe", 148 | "sppsvc.exe": "\windows\system32\sppsvc.exe"} 149 | 150 | # expected process parent/child relationship 151 | parent_child = {'services.exe': ["sppsvc.exe", "taskhost.exe", "mscorsvw.exe", "TPAutoConnSvc", "SearchIndexer", "svchost.exe", "taskhost.exe", "spoolsv.exe"], 152 | 'System': ["smss.exe"], 'csrss.exe': ["conhost.exe"], 153 | 'svchost.exe': ["WmiPrvSE.exe", "audiodg.exe"], 154 | 'wininit.exe': ["services.exe", "lsass.exe", "lsm.exe"] 155 | } 156 | 157 | # expected process sessions 158 | session0_processes = ["wininit.exe", "services.exe", "svchost.exe", "lsm.exe", "lsass.exe"] 159 | session1_processes = ["winlogon.exe"] 160 | 161 | # VOLATILITY PROFILES ================================================================ 162 | profiles = ["VistaSP0x86", "VistaSP0x64", "VistaSP1x86", "VistaSP1x64", "VistaSP2x86", "VistaSP2x64", 163 | "Win2003SP0x86", "Win2003SP1x86", "Win2003SP1x64", "Win2003SP2x86", "Win2003SP2x64", 164 | "Win2008SP1x86", "Win2008SP1x64", "Win2008SP2x86", "Win2008SP2x64", "Win2008R2SP0x64", "Win2008R2SP1x64", 165 | "Win2012R2x64", "Win2012x64", 166 | "Win7SP0x86", "Win7SP0x64", "Win7SP1x86", "Win7SP1x64", 167 | "Win8SP0x86", "Win8SP0x64", "Win8SP1x86", "Win8SP1x64", 168 | "WinXPSP2x86", "WinXPSP1x64", "WinXPSP2x64", "WinXPSP3x86"] 169 | 170 | preferred_profiles = ["Win7SP0x86", "Win7SP0x64", "Win7SP1x86", "Win7SP1x64"] 171 | 172 | 173 | # PRINT VOLDIFF BANNER ================================================================ 174 | def print_voldiff_banner(): 175 | print (" _ ___ _ __ __ ") 176 | print (" /\ /\___ | | / (_)/ _|/ _|") 177 | print (" \ \ / / _ \| | / /\ / | |_| |_ ") 178 | print (" \ V / (_) | |/ /_//| | _| _|") 179 | print (" \_/ \___/|_/___,' |_|_| |_| ") 180 | print ("\nVolDiff: Malware Memory Footprint Analysis (v%s)\n" % version) 181 | 182 | 183 | # PRINT HELP SECTION ================================================================ 184 | def print_help(): 185 | print ("Usage: ./VolDiff.py [BASELINE_IMAGE] INFECTED_IMAGE PROFILE [OPTIONS]") 186 | print ("\nOptions:") 187 | print ("--help display this help and exit") 188 | print ("--version display version information and exit") 189 | print ("--dependencies display information about script dependencies and exit") 190 | print ("--malware-checks hunt and report suspicious anomalies (slow, recommended)") 191 | print ("--output-dir [dir] custom directory to store analysis results") 192 | print ("--no-report do not create a report") 193 | print ("\nTested using Volatility 2.5 (vol.py) on Windows 7 images.") 194 | sys.exit() 195 | 196 | 197 | # PRINT VERSION INFORMATION ================================================================ 198 | def print_version(): 199 | print ("This is a free software: you are free to change and redistribute it.") 200 | print ("There is no warranty, to the extent permitted by law.") 201 | print ("Written by @aim4r. Report bugs to voldiff[@]gmail.com.") 202 | sys.exit() 203 | 204 | 205 | # PRINT DEPENDENCIES ================================================================ 206 | def print_dependencies(): 207 | print ("Requires volatility 2.5 (vol.py) to be installed.") 208 | sys.exit() 209 | 210 | 211 | # VERIFY PATH TO VOLATILITY EXISTS ================================================================ 212 | def check_volatility_path(path): 213 | if os.path.isfile(path): 214 | return True 215 | for p in os.environ["PATH"].split(os.pathsep): 216 | full_path = os.path.join(p, path) 217 | if os.path.exists(full_path): 218 | return True 219 | return False 220 | 221 | 222 | # VERIFY ENOUGH ARGUMENTS ARE SUPPLIED ================================================================ 223 | def check_enough_arguments_supplied(n=4): 224 | if len(sys.argv) < n: 225 | print("Not enough arguments supplied. Please use the --help option for help.") 226 | sys.exit() 227 | 228 | 229 | # SET PROFILE AND FIND PATH TO MEMORY IMAGE(S) ================================================================ 230 | def check_profile(pr): 231 | if pr not in profiles: 232 | print ("Please specify a valid Volatility Windows profile for use (such as Win7SP1x64).") 233 | sys.exit() 234 | if pr not in preferred_profiles: 235 | print( 236 | "WARNING: This script was only tested using Windows 7 profiles. The specified profile (%s) seems different!" % pr) 237 | else: 238 | print ("Profile: %s" % pr) 239 | return 240 | 241 | 242 | # COMPLETION AND CLEANUP FUNCTION ================================================================ 243 | def script_completion(start_time): 244 | if 'report' in globals(): 245 | report.write("\n\nEnd of report.") 246 | report.close() 247 | open_report(output_dir + "/VolDiff_Report.txt") 248 | notify_completion("VolDiff execution completed.") 249 | shutil.rmtree(output_dir + '/tmpfolder') 250 | completion_time = time.time() - start_time 251 | a = int(completion_time / 60) 252 | b = int(completion_time % 60) 253 | if 'devnull' in globals(): 254 | devnull.close() 255 | print("\nVolDiff execution completed in %s minutes and %s seconds." % (a, b)) 256 | sys.exit() 257 | 258 | 259 | # DIFFING RESULTS ================================================================ 260 | def diff_files(path1, path2, diffpath): 261 | with open(path1, "r") as file1: 262 | with open(path2, "r") as file2: 263 | diff = difflib.unified_diff(file1.readlines(), file2.readlines()) 264 | with open(diffpath, 'w+') as file3: 265 | print >> file3, ''.join(list(diff)) 266 | file3.seek(0) 267 | lines = file3.readlines() 268 | file3.seek(0) 269 | for line in lines: 270 | if line.startswith("+") and not line.startswith("+++"): 271 | file3.write(line[1:]) 272 | file3.truncate() 273 | return 274 | 275 | 276 | # REPORT CREATION ================================================================ 277 | def report_plugin(plugin, header_lines=0, threshold=diff_output_threshold): 278 | report.write("\n\nNew %s entries." % plugin) 279 | report.write("\n==========================================================================================================================\n") 280 | if header_lines != 0: 281 | with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f: 282 | for i in range(header_lines): 283 | line = next(f, '').strip() 284 | report.write(line + "\n") 285 | line_counter = 0 286 | with open(output_dir + "/" + plugin + "/diff_" + plugin + ".txt") as diff: 287 | for line in diff: 288 | line_counter += 1 289 | if line_counter < threshold: 290 | report.write(line) 291 | else: 292 | report.write("\nWarning: too many new entries to report, output truncated!\n") 293 | break 294 | return 295 | 296 | 297 | # OPENING REPORT ================================================================ 298 | def open_report(report_path): 299 | if os.name == 'posix': 300 | p = Popen(['xdg-open', report_path], stdout=devnull, stderr=devnull) 301 | p.wait() 302 | elif os.name == 'mac': 303 | p = Popen(['open', report_path], stdout=devnull, stderr=devnull) 304 | p.wait() 305 | elif os.name == 'nt': 306 | p = Popen(['cmd', '/c', 'start', report_path], stdout=devnull, stderr=devnull) # cmd /c start [filename] 307 | p.wait() 308 | 309 | 310 | # NOTIFYING ABOUT SCRIPT COMPLETION ================================================================ 311 | def notify_completion(message): 312 | if os.name == 'posix': 313 | p = Popen(['notify-send', message], stdout=devnull, stderr=devnull) 314 | p.wait() 315 | 316 | 317 | # MALWARE ANALYSIS FUNCTIONS ================================================================ 318 | def open_full_plugin(plugin="psscan", lines_to_ignore=2, state="infected"): 319 | if os.path.isfile(output_dir + "/" + plugin + "/" + state + "_" + plugin + ".txt"): 320 | f = open(output_dir + "/" + plugin + "/" + state + "_" + plugin + ".txt") 321 | else: 322 | f = open(output_dir + "/" + plugin + "/" + plugin + ".txt") 323 | for i in xrange(lines_to_ignore): 324 | next(f, '') 325 | return f 326 | 327 | 328 | def open_diff_plugin(plugin="psscan", lines_to_ignore=2): 329 | if os.path.isfile(output_dir + "/" + plugin + "/diff_" + plugin + ".txt"): 330 | f = open(output_dir + "/" + plugin + "/diff_" + plugin + ".txt") 331 | else: 332 | f = open(output_dir + "/" + plugin + "/" + plugin + ".txt") 333 | for i in xrange(lines_to_ignore): 334 | next(f, '') 335 | return f 336 | 337 | 338 | def anomaly_search(plugin, regex_to_include, ignorecase='yes', regex_to_exclude='', diff="diff"): 339 | match_list = [] 340 | if diff == "diff": 341 | f = open_diff_plugin(plugin) 342 | else: 343 | f = open_full_plugin(plugin) 344 | for line in f: 345 | if ignorecase == 'yes': 346 | if re.search(regex_to_include, line, re.IGNORECASE): 347 | if regex_to_exclude == '': 348 | match_list.append(line) 349 | elif not re.search(regex_to_exclude, line, re.IGNORECASE): 350 | match_list.append(line) 351 | else: 352 | if re.search(regex_to_include, line): 353 | if regex_to_exclude == '': 354 | match_list.append(line) 355 | elif not re.search(regex_to_exclude, line): 356 | match_list.append(line) 357 | f.close() 358 | return match_list 359 | 360 | 361 | def anomaly_search_inverted(plugin, regex_to_exclude, ignorecase='yes', regex_to_include=''): 362 | match_list = [] 363 | f = open_diff_plugin(plugin) 364 | for line in f: 365 | if ignorecase == 'yes': 366 | if not re.search(regex_to_exclude, line, re.IGNORECASE): 367 | if regex_to_include == '': 368 | match_list.append(line) 369 | elif re.search(regex_to_include, line, re.IGNORECASE): 370 | match_list.append(line) 371 | else: 372 | if not re.search(regex_to_exclude, line): 373 | if regex_to_include == '': 374 | match_list.append(line) 375 | elif re.search(regex_to_include, line): 376 | match_list.append(line) 377 | f.close() 378 | return match_list 379 | 380 | 381 | def report_anomalies(headline, anomaly_list, delim="=", plugin="", header_lines=0, threshold=ma_output_threshold): 382 | if len(anomaly_list) != 0: 383 | report.write("\n\n%s" % headline) 384 | if delim == "=": 385 | report.write( 386 | "\n==========================================================================================================================\n") 387 | elif delim == '-': 388 | report.write( 389 | "\n--------------------------------------------------------------------------------------------------------------------------\n") 390 | if header_lines != 0 and plugin != "": 391 | if os.path.isfile(output_dir + "/" + plugin + "/infected_" + plugin + ".txt"): 392 | with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f: 393 | for i in range(header_lines): 394 | line = next(f, '').strip() 395 | report.write(line + "\n") 396 | else: 397 | with open(output_dir + "/" + plugin + "/" + plugin + ".txt") as f: 398 | for i in range(header_lines): 399 | line = next(f, '').strip() 400 | report.write(line + "\n") 401 | if len(anomaly_list) > threshold: 402 | anomaly_list_to_report = anomaly_list[0:threshold] 403 | anomaly_list_to_report.append("\nWarning: too many entries to report, output truncated!\n") 404 | else: 405 | anomaly_list_to_report = anomaly_list 406 | for line in anomaly_list_to_report: 407 | report.write(line) 408 | return 409 | 410 | 411 | def extract_substrings(input_list, regex): 412 | extracted_list = [] 413 | for entry in input_list: 414 | subentries = entry.split(' ') 415 | for subentry in subentries: 416 | if re.search(regex, subentry, re.IGNORECASE): 417 | extracted_list.append(subentry) 418 | return extracted_list 419 | 420 | 421 | def tidy_list(input_list): 422 | updatedlist = [] 423 | for entry in input_list: 424 | if not re.search("\\n", entry): 425 | entry += '\n' 426 | updatedlist.append(entry) 427 | updatedlist = sorted(set(updatedlist)) 428 | return updatedlist 429 | 430 | 431 | def find_ips_domains_emails(plugin): 432 | f = open_diff_plugin(plugin, 0) 433 | ips = [] 434 | ips_to_report = [] 435 | ips_regex_exclude = r"127\.0\.0\.1|0\.0\.0\.0" 436 | for line in f: 437 | if re.search(ips_regex, line, re.IGNORECASE): 438 | ips += re.findall(ips_regex, line, re.IGNORECASE) 439 | for ip in ips: 440 | if not re.search(ips_regex_exclude, ip, re.IGNORECASE): 441 | ips_to_report.append(ip) 442 | domains = [] 443 | f.seek(0) 444 | for line in f: 445 | if re.search(domains_regex_http, line, re.IGNORECASE): 446 | domains += re.findall(domains_regex_http, line, re.IGNORECASE) 447 | if re.search(domains_regex_ftp, line, re.IGNORECASE): 448 | domains += re.findall(domains_regex_ftp, line, re.IGNORECASE) 449 | if re.search(domains_regex_file, line, re.IGNORECASE): 450 | domains += re.findall(domains_regex_file, line, re.IGNORECASE) 451 | emails = [] 452 | f.seek(0) 453 | for line in f: 454 | if re.search(emails_regex, line, re.IGNORECASE): 455 | emails += re.findall(emails_regex, line, re.IGNORECASE) 456 | ips_domains_emails = ips_to_report + domains + emails 457 | ips_domains_emails = tidy_list(ips_domains_emails) 458 | f.close() 459 | return ips_domains_emails 460 | 461 | 462 | def get_pids(procname, plugin="psscan"): 463 | pids = [] 464 | if procname == "": 465 | return pids 466 | f = open_full_plugin(plugin, 2) 467 | for line in f: 468 | if re.search(' ' + procname + ' ', line, re.IGNORECASE): 469 | pids.append(re.sub(' +', ' ', line).split(' ')[2]) 470 | pids = sorted(set(pids)) 471 | f.close() 472 | return pids 473 | 474 | 475 | def get_associated_process_lines_pids(pids, plugin="psscan"): 476 | f = open_full_plugin(plugin, 2) 477 | associated_psscan_lines = [] 478 | for line in f: 479 | for pid in pids: 480 | if re.sub(' +', ' ', line).split(' ')[2] == str(pid): 481 | associated_psscan_lines.append(line) 482 | f.close() 483 | return associated_psscan_lines 484 | 485 | 486 | def get_associated_process_lines_ppids(ppids, plugin="psscan"): 487 | f = open_full_plugin(plugin, 2) 488 | associated_psscan_lines = [] 489 | for line in f: 490 | for ppid in ppids: 491 | if re.sub(' +', ' ', line).split(' ')[3] == str(ppid): 492 | associated_psscan_lines.append(line) 493 | f.close() 494 | return associated_psscan_lines 495 | 496 | 497 | def get_childs_of(pids): 498 | f = open_full_plugin("psscan", 2) 499 | childs = [] 500 | for line in f: 501 | for pid in pids: 502 | ppid = re.sub(' +', ' ', line).split(' ')[3] 503 | if ppid == str(pid): 504 | childs.append(re.sub(' +', ' ', line).split(' ')[2]) 505 | childs = sorted(set(childs)) 506 | f.close() 507 | return childs 508 | 509 | 510 | def get_parent_pids_of(childs): 511 | f = open_full_plugin("psscan", 2) 512 | parents = [] 513 | for line in f: 514 | for child in childs: 515 | if re.sub(' +', ' ', line).split(' ')[2] == child: 516 | parents.append(re.sub(' +', ' ', line).split(' ')[3]) 517 | parents = sorted(set(parents)) 518 | f.close() 519 | return parents 520 | 521 | 522 | def get_procnames(pids): 523 | f = open_full_plugin("psscan", 2) 524 | procnames = [] 525 | for line in f: 526 | for pid in pids: 527 | if re.sub(' +', ' ', line).split(' ')[2] == pid: 528 | procnames.append(re.sub(' +', ' ', line).split(' ')[1]) 529 | f.close() 530 | return procnames 531 | 532 | 533 | def get_all_pids(exception_regex=''): 534 | f = open_full_plugin("psscan", 2) 535 | pids = [] 536 | for line in f: 537 | if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE): 538 | continue 539 | else: 540 | pid = re.sub(' +', ' ', line).split(' ')[2] 541 | if pid != "0": 542 | pids.append(pid) 543 | pids = sorted(set(pids)) 544 | f.close() 545 | return pids 546 | 547 | 548 | def get_diff_pids(exception_regex=''): 549 | f = open_diff_plugin("psscan", 2) 550 | pids = [] 551 | for line in f: 552 | if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE): 553 | continue 554 | else: 555 | pid = re.sub(' +', ' ', line).split(' ')[2] 556 | if pid != "0": 557 | pids.append(pid) 558 | pids = sorted(set(pids)) 559 | f.close() 560 | return pids 561 | 562 | 563 | def get_procname(pid, plugin='psscan'): 564 | f = open_full_plugin(plugin, 2) 565 | procnamee = "" 566 | for line in f: 567 | if re.search(r"[a-zA-Z\.]\s+%s " % pid, line, re.IGNORECASE): 568 | procnamee = (re.sub(' +', ' ', line).split(' ')[1]) 569 | break 570 | procnamee = str(procnamee) 571 | f.close() 572 | return procnamee 573 | 574 | 575 | def get_all_procnames(plugin='psscan', exception_regex=''): 576 | f = open_full_plugin(plugin, 2) 577 | procnames = [] 578 | for line in f: 579 | if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE): 580 | continue 581 | else: 582 | procnames.append(re.sub(' +', ' ', line).split(' ')[1]) 583 | procnames = sorted(set(procnames)) 584 | f.close() 585 | return procnames 586 | 587 | 588 | def get_all_ppids(exception_regex=''): 589 | f = open_full_plugin("psscan", 2) 590 | ppids = [] 591 | for line in f: 592 | if exception_regex != '' and re.search(exception_regex, line, re.IGNORECASE): 593 | continue 594 | elif re.sub(' +', ' ', line).split(' ')[2] != "0": 595 | ppids.append(re.sub(' +', ' ', line).split(' ')[3]) 596 | ppids = sorted(set(ppids)) 597 | f.close() 598 | return ppids 599 | 600 | 601 | def get_session(pid): 602 | session = "" 603 | f = open_full_plugin("pslist", 2) 604 | for line in f: 605 | if re.search(' ' + str(pid) + ' ', line, re.IGNORECASE): 606 | session = re.sub(' +', ' ', line).split(' ')[6] 607 | break 608 | f.close() 609 | return session 610 | 611 | 612 | def get_execpath(pid): 613 | execpath = '' 614 | procnamep = get_procname(pid) 615 | f = open_full_plugin("dlllist", 0) 616 | for line in f: 617 | if re.search(procnamep + ' pid.*' + str(pid), line, re.IGNORECASE): 618 | command_line = next(f, '') 619 | execpath = re.sub("Command line : ", "", command_line) 620 | execpath = re.sub(".:", "", execpath) 621 | execpath = re.sub(" .*", "", execpath) 622 | execpath = re.sub("\n", "", execpath) 623 | f.close() 624 | return execpath 625 | 626 | 627 | def get_cmdline(pid): 628 | cmdline = [] 629 | procnamec = get_procname(pid) 630 | f = open_full_plugin("cmdline", 0) 631 | for line in f: 632 | if re.search(procnamec + ' pid.* ' + pid, line, re.IGNORECASE): 633 | cmdline.append(line) 634 | line = next(f, '') 635 | cmdline.append(line) 636 | break 637 | if cmdline: 638 | if not re.search("Command", cmdline[1], re.IGNORECASE): 639 | cmdline = [] 640 | f.close() 641 | return cmdline 642 | 643 | 644 | def deadproc_activethreads(): 645 | f = open_full_plugin("psxview", 2) 646 | dead_proc_active_threads = [] 647 | for line in f: 648 | if 'UTC' in str(re.sub(' +', ' ', line).split(' ')[9:]) and re.sub(' +', ' ', line).split(' ')[5] == "True": 649 | dead_proc_active_threads.append(line) 650 | f.close() 651 | return dead_proc_active_threads 652 | 653 | 654 | def get_hosts_contents(memory_image_file): 655 | hostscontent = [] 656 | f = open_full_plugin("filescan", 2) 657 | qaddressb = "" 658 | for line in f: 659 | if re.search("etc\\\hosts$", line, re.IGNORECASE): 660 | qaddressb = re.sub(' +', ' ', line).split(' ')[0] 661 | break 662 | if qaddressb != "": 663 | hostsfolder = tmpfolder + "hosts/" 664 | if not os.path.isdir(hostsfolder): 665 | os.makedirs(hostsfolder) 666 | process_var = Popen([path_to_volatility, "--profile", profile, "-f", memory_image_file, "dumpfiles", "-Q", qaddressb, "-D", hostsfolder], stdout=devnull, stderr=devnull) 667 | process_var.wait() 668 | dumped_hosts_filename = os.listdir(hostsfolder) 669 | if len(dumped_hosts_filename) == 1: 670 | with open(hostsfolder + str(dumped_hosts_filename[0]), mode='rb') as hosts: 671 | for line in hosts: 672 | if not re.search("^#", line) and re.search(" ", line): 673 | hostscontent.append(line) 674 | hostscontent = sorted(set(hostscontent)) 675 | f.close() 676 | return hostscontent 677 | 678 | 679 | def filter_new_services(): 680 | filtered_services = [] 681 | diff_svcscan = open_diff_plugin("svcscan", 0) 682 | baseline_svcscan = open_full_plugin("svcscan", 0, "baseline") 683 | for line in diff_svcscan: 684 | if line not in baseline_svcscan and not re.search("Offset:", line, re.IGNORECASE): 685 | filtered_services.append(line) 686 | baseline_svcscan.seek(0) 687 | filtered_services = set(filtered_services) 688 | baseline_svcscan.close() 689 | diff_svcscan.close() 690 | return filtered_services 691 | 692 | 693 | def get_associated_services(pid): 694 | services = [] 695 | full_svcscan = open_full_plugin("svcscan", 0) 696 | for line in full_svcscan: 697 | if re.search("Process ID: " + str(pid) + "\n", line, re.IGNORECASE): 698 | services.append("\n") 699 | services.append(line) 700 | for i in xrange(5): 701 | line = next(full_svcscan, '') 702 | services.append(line) 703 | full_svcscan.close() 704 | return services 705 | 706 | 707 | def get_malfind_pids(): 708 | malfind_pids = [] 709 | f = open_diff_plugin("malfind", 0) 710 | for line in f: 711 | if re.search("Address:", line): 712 | malfind_pids.append(re.sub(' +', ' ', line).split(' ')[3]) 713 | malfind_pids = sorted(set(malfind_pids)) 714 | f.close() 715 | return malfind_pids 716 | 717 | 718 | def get_malfind_injections(pid, m="dual"): 719 | malfind_injections = [] 720 | f = open_diff_plugin("malfind", 0) 721 | if m == "dual": 722 | n = 6 723 | else: 724 | n = 7 725 | for line in f: 726 | if re.search("Pid: " + str(pid) + " ", line): 727 | malfind_injections.append("\n") 728 | malfind_injections.append(line) 729 | for i in xrange(n): 730 | line = next(f, '') 731 | malfind_injections.append(line) 732 | f.close() 733 | return malfind_injections 734 | 735 | 736 | def analyse_registry(pid): 737 | rhit = False 738 | registry_analysis_matrix = {"\nCollects information about system": registry_infogathering_regex, 739 | "\nQueries / modifies proxy settings": registry_proxy_settings_regex, 740 | "\nReads information about supported languages": registry_locale_regex, 741 | "\nIdentifies machine name": registry_hostname_regex, 742 | "\nIdentifies installed programs": registry_installed_programs_regex, 743 | "\nQueries / modifies remote control settings": registry_remote_control_regex, 744 | "\nQueries / modifies firewall settings": registry_firewall_regex, 745 | "\nQueries / modifies service settings": registry_services_regex, 746 | "\nQueries / modifies network settings": registry_network_regex, 747 | "\nHas access to autorun registry keys": registry_autorun_regex, 748 | "\nQueries / modifies the Windows command processor": registry_command_processor_regex, 749 | "\nQueries / modifies encryption seettings": registry_crypto_regex, 750 | "\nQueries / modifies file association settings": registry_file_associations_regex, 751 | "\nQueries / modifies security seettings": registry_ie_security_regex, 752 | } 753 | for reg_key in registry_analysis_matrix: 754 | registry = anomaly_search("handles", registry_analysis_matrix[reg_key], "yes", "", "diff") 755 | registry_to_report = [] 756 | for key in registry: 757 | if re.search(" " + str(pid) + " ", key, re.IGNORECASE) and re.search("Key", key, re.IGNORECASE): 758 | a = re.sub(' +', ' ', key).split(' ')[5:] 759 | b = re.sub('\n', '', ' '.join(a)) 760 | registry_to_report.append(b) 761 | if len(registry_to_report) > 0: 762 | if not rhit: 763 | report.write("\n\nInteresting registry handles:") 764 | report.write( 765 | "\n--------------------------------------------------------------------------------------------------------------------------\n") 766 | rhit = True 767 | report_string = "" 768 | registry_to_report = sorted(set(registry_to_report)) 769 | for regkey in registry_to_report: 770 | report_string += " " + regkey + "\n" 771 | report.write(reg_key + ":\n" + report_string) 772 | 773 | 774 | def analyse_imports(pid): 775 | import_analysis_matrix = {"Can create new desktops ": ransomware_imports, 776 | "Can track keyboard strokes ": keylogger_imports, 777 | "Can extract passwords ": password_extract_imports, 778 | "Can access the clipboard ": clipboard_imports, 779 | "Can inject code to other processes ": process_injection_imports, 780 | "Can bypass UAC ": uac_bypass_imports, 781 | "Can use antidebug techniques ": anti_debug_imports, 782 | "Can receive or send files from or to internet ": web_imports, 783 | "Can listen for inbound connections ": listen_imports, 784 | "Can create or start services ": service_imports, 785 | "Can restart or shutdown the system ": shutdown_imports, 786 | "Can interact with the registry ": registry_imports, 787 | "Can create or write to files ": file_imports, 788 | "Can create atoms ": atoms_imports, 789 | "Can identify machine time ": localtime_imports, 790 | "Can interact with or query device drivers ": driver_imports, 791 | "Can enumerate username ": username_imports, 792 | "Can identify machine version information ": machine_version_imports, 793 | "Can query startup information ": startup_imports, 794 | "Can enumerate free disk space ": diskspace_imports, 795 | "Can enumerate system information ": sysinfo_imports 796 | } 797 | impscanfolder = tmpfolder + "impscan/" 798 | hit = False 799 | if os.path.isfile(impscanfolder + str(pid) + ".txt"): 800 | for susp_imports_codename in import_analysis_matrix: 801 | regex = import_analysis_matrix[susp_imports_codename] 802 | susp_functions = [] 803 | with open(impscanfolder + str(pid) + ".txt", "r") as imports: 804 | for function in imports: 805 | if re.search(regex, function, re.IGNORECASE): 806 | susp_functions.append(re.sub(' +', ' ', function).split(' ')[3]) 807 | if len(susp_functions) > 0: 808 | if not hit: 809 | report.write("\n\nInteresting imports:") 810 | report.write( 811 | "\n--------------------------------------------------------------------------------------------------------------------------\n") 812 | hit = True 813 | report_string = "" 814 | susp_functions = sorted(set(susp_functions)) 815 | for function in susp_functions: 816 | if function == susp_functions[-1]: 817 | report_string += re.sub("\n", "", function) 818 | else: 819 | report_string += (re.sub("\n", "", function) + ", ") 820 | report.write(susp_imports_codename + "(" + report_string + ").\n") 821 | 822 | 823 | def strings(filepath, minimum=4): 824 | with open(filepath, "rb") as f: 825 | result = "" 826 | for c in f.read(): 827 | if c in string.printable: 828 | result += c 829 | continue 830 | if len(result) >= minimum: 831 | yield result 832 | result = "" 833 | 834 | 835 | def analyse_strings(pid): 836 | strings_analysis_matrix = {"IP address(es)": ips_regex, 837 | "Email(s)": emails_regex, 838 | "HTTP URL(s)": domains_regex_http, 839 | "FTP URL(s)": domains_regex_ftp, 840 | "File URL(s)": domains_regex_file, 841 | "Web related keyword(s)": web_regex_str, 842 | "Keylogger keyword(s)": keylogger_regex_str, 843 | "Password keyword(s)": password_regex_str, 844 | "RAT keyword(s)": rat_regex_str, 845 | "Tool(s)": tool_regex_str, 846 | "Banking keyword(s)": banking_regex_str, 847 | "Social website(s)": socialsites_regex_str, 848 | "Antivirus keyword(s)": antivirus_regex_str, 849 | "Anti-sandbox keyword(s)": sandbox_regex_str, 850 | "Virtualisation keyword(s)": virtualisation_regex_str, 851 | "Sysinternal tool(s)": sysinternals_regex_str, 852 | "Powershell keyword(s)": powershell_regex_str, 853 | "SQL keyword(s)": sql_regex_str, 854 | "Shell keyword(s)": shell_regex_str, 855 | "Information gathering keyword(s)": infogathering_regex_str, 856 | "Executable file(s)": exec_regex_str, 857 | "Encryption keyword(s)": crypto_regex_str, 858 | "Filepath(s)": filepath_regex_str, 859 | "Browser keyword(s)": browser_regex_str, 860 | "Misc keyword(s)": other_regex_str 861 | } 862 | dumpfolder = tmpfolder + str(pid) + "/" 863 | filelist = os.listdir(dumpfolder) 864 | hit = False 865 | for susp_strings_codename in strings_analysis_matrix: 866 | regex = strings_analysis_matrix[susp_strings_codename] 867 | susp_strings = [] 868 | for f in filelist: 869 | for stringa in strings(dumpfolder + f): 870 | if re.search(regex, stringa, re.IGNORECASE): 871 | for i in re.findall(regex, stringa, re.IGNORECASE): 872 | susp_strings.append(i) 873 | if len(susp_strings) > 0: 874 | if not hit: 875 | report.write("\n\nSuspicious strings from process memory:") 876 | report.write("\n--------------------------------------------------------------------------------------------------------------------------\n") 877 | hit = True 878 | report_string = "" 879 | susp_strings = sorted(set(susp_strings)) 880 | for susp_string in susp_strings: 881 | if susp_string == susp_strings[-1]: 882 | report_string += re.sub("\n", "", susp_string) 883 | else: 884 | report_string += (re.sub("\n", "", susp_string) + ", ") 885 | report.write(susp_strings_codename + ": " + report_string + "\n") 886 | 887 | 888 | def check_expected_parent(pid): 889 | fl = False 890 | expected_parent = "" 891 | childname = get_procname(pid, 'psscan') 892 | parent = "" 893 | for parent in parent_child: 894 | if childname in parent_child[parent]: 895 | fl = True 896 | expected_parent = parent 897 | break 898 | if fl: 899 | actual_parent = get_procname(get_parent_pids_of([pid, ])[0], "psscan") 900 | if actual_parent.lower() != parent.lower(): 901 | j = get_associated_process_lines_pids(get_pids(actual_parent)) 902 | l = get_associated_process_lines_pids(get_pids(expected_parent)) 903 | k = get_associated_process_lines_pids([pid, ]) 904 | report_anomalies("Unexpected parent process (" + actual_parent + " instead of " + expected_parent + "):", k + j + l, '-', "psscan", 2) 905 | 906 | 907 | def get_remote_share_handles(pid): 908 | share_handles_to_report = [] 909 | remote_share = anomaly_search("handles", "Device\\\(LanmanRedirector|Mup)", 'yes', '', "diff") 910 | for share_handle in remote_share: 911 | if re.sub(' +', ' ', share_handle).split(' ')[1] == pid: 912 | share_handles_to_report.append(share_handle) 913 | return share_handles_to_report 914 | 915 | 916 | def get_raw_sockets(pid): 917 | raw_sockets_to_report = [] 918 | raw_sockets = anomaly_search("handles", "\\\Device\\\RawIp", 'yes', '', "diff") 919 | for raw_socket in raw_sockets: 920 | if re.sub(' +', ' ', raw_socket).split(' ')[1] == pid: 921 | raw_sockets_to_report.append(raw_socket) 922 | return raw_sockets_to_report 923 | 924 | 925 | def get_md5(pid): 926 | md5 = "" 927 | dump_folder = tmpfolder + str(pid) + "/" 928 | filelist = os.listdir(dump_folder) 929 | for f in filelist: 930 | if f == "executable." + str(pid) + ".exe": 931 | md5 = hashlib.md5(open(dump_folder + f).read()).hexdigest() 932 | break 933 | return md5 934 | 935 | 936 | def report_virustotal_md5_results(md5, api): 937 | url = "https://www.virustotal.com/vtapi/v2/file/report" 938 | parameters = {"resource": md5, "apikey": api} 939 | data = urllib.urlencode(parameters) 940 | req = urllib2.Request(url, data) 941 | response_dict = {} 942 | network_error = False 943 | try: 944 | response = urllib2.urlopen(req) 945 | json = response.read() 946 | if json != "": 947 | response_dict = simplejson.loads(json) 948 | except urllib2.URLError: 949 | network_error = True 950 | if not network_error: 951 | report.write("\n\nVirusTotal scan results:") 952 | report.write("\n--------------------------------------------------------------------------------------------------------------------------\n") 953 | report.write("MD5 value: " + md5 + "\n") 954 | if "response_code" in response_dict: 955 | if response_dict["response_code"] == 1: 956 | report.write("VirusTotal scan date: " + str(response_dict["scan_date"]) + "\n") 957 | report.write("VirusTotal engine detections: " + str(response_dict["positives"]) + "/" + str(response_dict["total"]) + "\n") 958 | report.write("Link to VirusTotal report: " + str(response_dict["permalink"]) + "\n") 959 | else: 960 | report.write("Could not find VirusTotal scan results for the MD5 value above.\n") 961 | else: 962 | report.write("VirusTotal request rate limit reached, could not retrieve results.\n") 963 | 964 | 965 | def main(): 966 | # PRINT VOLDIFF BANNER ================================================================ 967 | print_voldiff_banner() 968 | global output_dir 969 | global report 970 | global tmpfolder 971 | global profile 972 | global baseline_memory_image 973 | global infected_memory_image 974 | global memory_image 975 | 976 | # CHECK THAT VOL.PY IS INSTALLED ================================================================ 977 | if not check_volatility_path(path_to_volatility): 978 | print("vol.py does not seem to be installed. Please ensure that volatility is installed/functional before using VolDiff.") 979 | sys.exit() 980 | 981 | # READ SYS.ARGV VARIABLES ================================================================ 982 | if not len(sys.argv) > 1: 983 | print_help() 984 | elif "--help" in sys.argv: 985 | print_help() 986 | elif "--version" in sys.argv: 987 | print_version() 988 | elif "--dependencies" in sys.argv: 989 | print_dependencies() 990 | if "--output-dir" in sys.argv: 991 | check_enough_arguments_supplied(5) 992 | else: 993 | check_enough_arguments_supplied(3) 994 | if os.path.isfile(sys.argv[2]): 995 | mode = "dual" 996 | if os.path.isfile(sys.argv[1]): 997 | baseline_memory_image = sys.argv[1] 998 | print ("Path to baseline memory image: %s" % baseline_memory_image) 999 | else: 1000 | print ("Please specify a valid path to a baseline memory image.") 1001 | sys.exit() 1002 | infected_memory_image = sys.argv[2] 1003 | print ("Path to infected memory image: %s" % infected_memory_image) 1004 | 1005 | if len(sys.argv) == 3: 1006 | print ("Profile is not specified. Please specify a profile to use (such as Win7SP1x64).") 1007 | sys.exit() 1008 | else: 1009 | check_profile(sys.argv[3]) 1010 | profile = sys.argv[3] 1011 | else: 1012 | mode = "standalone" 1013 | if os.path.isfile(sys.argv[1]): 1014 | memory_image = sys.argv[1] 1015 | print ("Only one memory image specified: standalone mode") 1016 | print ("Path to memory image: %s" % memory_image) 1017 | else: 1018 | print ("Please specify a valid path to a baseline memory image.") 1019 | sys.exit() 1020 | if len(sys.argv) == 2: 1021 | print ("Profile is not specified. Please specify a profile to use (such as Win7SP1x64)!") 1022 | sys.exit() 1023 | else: 1024 | check_profile(sys.argv[2]) 1025 | profile = sys.argv[2] 1026 | 1027 | # CREATE FOLDER TO STORE OUTPUT ================================================================ 1028 | starttime = time.time() 1029 | output_dir = 'VolDiff_' + datetime.datetime.now().strftime("%d-%m-%Y_%H:%M") 1030 | if os.name == 'nt': 1031 | output_dir = 'VolDiff_' + datetime.datetime.now().strftime("%d-%m-%Y_%H%M") # can't name file/dir with : 1032 | tmpval = False 1033 | for arg in sys.argv: 1034 | if tmpval: 1035 | output_dir = arg 1036 | tmpval = False 1037 | if arg == "--output-dir": 1038 | tmpval = True 1039 | tmpfolder = output_dir + '/tmpfolder/' 1040 | os.makedirs(tmpfolder) 1041 | 1042 | # RUN VOLATILITY PLUGINS ================================================================ 1043 | print ("\nRunning a selection of volatility plugins (time consuming):") 1044 | sub_procs = {} 1045 | file_dict = {} 1046 | proc_counter = 0 1047 | for plugin in plugins_to_run: 1048 | print("Volatility plugin %s execution in progress..." % plugin) 1049 | plugin_path = output_dir + '/' + plugin + '/' 1050 | os.makedirs(plugin_path) 1051 | if plugin == "mutantscan" or plugin == "handles" or plugin == "privs" or plugin == "envars": 1052 | option = "--silent" 1053 | elif plugin == "threads": 1054 | option = "-F OrphanThread" 1055 | elif plugin == "psxview": 1056 | option = "-R" 1057 | elif plugin == "malfind": 1058 | if mode == "dual": 1059 | dump_dir_baseline = output_dir + '/malfind/dump_dir_baseline/' 1060 | os.makedirs(dump_dir_baseline) 1061 | option = "--dump-dir=" + output_dir + "/malfind/dump_dir_baseline/" 1062 | file_dict[plugin + "baseline"] = open(output_dir + '/' + plugin + '/' + "baseline_" + plugin + ".txt", "w") 1063 | sub_procs[plugin + "baseline"] = Popen([path_to_volatility, "--profile", profile, "-f", baseline_memory_image, plugin, option], stdout=file_dict[plugin + "baseline"], stderr=devnull) 1064 | proc_counter += 1 1065 | if proc_counter >= max_concurrent_subprocesses: 1066 | for pr in sub_procs: 1067 | sub_procs[pr].wait() 1068 | proc_counter = 0 1069 | sub_procs = {} 1070 | dump_dir_infected = output_dir + '/malfind/dump_dir_infected/' 1071 | os.makedirs(dump_dir_infected) 1072 | option = "--dump-dir=" + output_dir + "/malfind/dump_dir_infected/" 1073 | file_dict[plugin + "infected"] = open(output_dir + '/' + plugin + '/' + "infected_" + plugin + ".txt", "w") 1074 | sub_procs[plugin + "infected"] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, option], stdout=file_dict[plugin + "infected"], stderr=devnull) 1075 | proc_counter += 1 1076 | if proc_counter >= max_concurrent_subprocesses: 1077 | for pr in sub_procs: 1078 | sub_procs[pr].wait() 1079 | proc_counter = 0 1080 | sub_procs = {} 1081 | else: 1082 | dump_dir = output_dir + '/malfind/dump_dir/' 1083 | os.makedirs(dump_dir) 1084 | option = "--dump-dir=" + output_dir + "/malfind/dump_dir/" 1085 | file_dict[plugin] = open(output_dir + '/' + plugin + '/' + plugin + ".txt", "w") 1086 | sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, option], stdout=file_dict[plugin], stderr=devnull) 1087 | proc_counter += 1 1088 | if proc_counter >= max_concurrent_subprocesses: 1089 | for pr in sub_procs: 1090 | sub_procs[pr].wait() 1091 | proc_counter = 0 1092 | sub_procs = {} 1093 | continue 1094 | elif plugin == "procdump": 1095 | option = "--dump-dir=" + output_dir + "/procdump/" 1096 | if mode == "dual": 1097 | sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, "-u", option], stdout=devnull, stderr=devnull) 1098 | proc_counter += 1 1099 | if proc_counter >= max_concurrent_subprocesses: 1100 | for pr in sub_procs: 1101 | sub_procs[pr].wait() 1102 | proc_counter = 0 1103 | sub_procs = {} 1104 | else: 1105 | sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, "-u", option], stdout=devnull, stderr=devnull) 1106 | proc_counter += 1 1107 | if proc_counter >= max_concurrent_subprocesses: 1108 | for pr in sub_procs: 1109 | sub_procs[pr].wait() 1110 | proc_counter = 0 1111 | sub_procs = {} 1112 | continue 1113 | else: 1114 | option = '' 1115 | # option set, running vol.py processes in //: 1116 | if mode == "dual": 1117 | file_dict[plugin + "baseline"] = open(output_dir + '/' + plugin + '/' + "baseline_" + plugin + ".txt", "w") 1118 | sub_procs[plugin + "baseline"] = Popen([path_to_volatility, "--profile", profile, "-f", baseline_memory_image, plugin, option], stdout=file_dict[plugin + "baseline"], stderr=devnull) 1119 | proc_counter += 1 1120 | if proc_counter >= max_concurrent_subprocesses: 1121 | for pr in sub_procs: 1122 | sub_procs[pr].wait() 1123 | proc_counter = 0 1124 | sub_procs = {} 1125 | file_dict[plugin + "infected"] = open(output_dir + '/' + plugin + '/' + "infected_" + plugin + ".txt", "w") 1126 | sub_procs[plugin + "infected"] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, option], stdout=file_dict[plugin + "infected"], stderr=devnull) 1127 | proc_counter += 1 1128 | if proc_counter >= max_concurrent_subprocesses: 1129 | for pr in sub_procs: 1130 | sub_procs[pr].wait() 1131 | proc_counter = 0 1132 | sub_procs = {} 1133 | else: 1134 | file_dict[plugin] = open(output_dir + '/' + plugin + '/' + plugin + ".txt", "w") 1135 | sub_procs[plugin] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, option], stdout=file_dict[plugin], stderr=devnull) 1136 | proc_counter += 1 1137 | if proc_counter >= max_concurrent_subprocesses: 1138 | for pr in sub_procs: 1139 | sub_procs[pr].wait() 1140 | proc_counter = 0 1141 | sub_procs = {} 1142 | # ensuring that all subprocesses are completed before proceeding: 1143 | for pr in sub_procs: 1144 | sub_procs[pr].wait() 1145 | for f in file_dict: 1146 | file_dict[f].close() 1147 | 1148 | # DEV MODE SWITCH ================================================================ 1149 | if "--devmode" in sys.argv: 1150 | raw_input('\nChange files and hit enter once ready.') 1151 | 1152 | # DIFF OUTPUT RESULTS ================================================================ 1153 | if mode == "dual": 1154 | print ("Diffing output results...") 1155 | for plugin in plugins_to_run: 1156 | if plugin != "procdump": 1157 | diff_files(output_dir + '/' + plugin + '/baseline_' + plugin + ".txt", 1158 | output_dir + '/' + plugin + '/infected_' + plugin + ".txt", 1159 | output_dir + '/' + plugin + '/diff_' + plugin + ".txt") 1160 | 1161 | if "--no-report" in sys.argv: 1162 | script_completion(starttime) 1163 | 1164 | # CREATE REPORT ================================================================ 1165 | report = open(output_dir + "/VolDiff_Report.txt", 'w') 1166 | 1167 | if mode == "dual": 1168 | report.write(" _ ___ _ __ __ \n") 1169 | report.write(" /\ /\___ | | / (_)/ _|/ _|\n") 1170 | report.write(" \ \ / / _ \| | / /\ / | |_| |_ \n") 1171 | report.write(" \ V / (_) | |/ /_//| | _| _|\n") 1172 | report.write(" \_/ \___/|_/___,' |_|_| |_| \n") 1173 | 1174 | report.write("\nVolatility analysis report generated by VolDiff v%s" % version) 1175 | report.write("\nDownload the latest VolDiff version from https://github.com/aim4r/VolDiff/") 1176 | report.write("\n\nBaseline memory image: %s" % baseline_memory_image) 1177 | report.write("\nInfected memory image: %s" % infected_memory_image) 1178 | report.write("\nProfile: %s" % profile) 1179 | report.write("\nDate and time: " + datetime.datetime.now().strftime("%d/%m/%Y %H:%M")) 1180 | 1181 | no_new_entries = [] 1182 | 1183 | for plugin in plugins_to_report: 1184 | if os.stat(output_dir + "/" + plugin + "/diff_" + plugin + ".txt").st_size == 0: 1185 | no_new_entries.append(plugin) 1186 | 1187 | # processing pslist and psscan output: 1188 | elif plugin == "pslist" or plugin == "psscan": 1189 | # store baseline pids in a list 1190 | with open(output_dir + "/" + plugin + "/baseline_" + plugin + ".txt") as baseline: 1191 | baseline_pids = [] 1192 | for line in baseline: 1193 | pid = re.sub(' +', ' ', line).split(' ')[2] 1194 | baseline_pids.append(pid) 1195 | sorted(set(baseline_pids)) 1196 | # store infected pids in a list 1197 | with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as infected: 1198 | infected_pids = [] 1199 | for line in infected: 1200 | pid = re.sub(' +', ' ', line).split(' ')[2] 1201 | infected_pids.append(pid) 1202 | sorted(set(infected_pids)) 1203 | # get the diff between both 1204 | diff_pids = [] 1205 | for pid in infected_pids: 1206 | if pid not in baseline_pids: 1207 | diff_pids.append(pid) 1208 | # print diff lines 1209 | if len(diff_pids) > 0: 1210 | report.write("\n\nNew %s entries." % plugin) 1211 | report.write( 1212 | "\n==========================================================================================================================\n") 1213 | with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f: 1214 | for i in range(2): 1215 | line = next(f, '').strip() 1216 | report.write(line + "\n") 1217 | for pid in diff_pids: 1218 | with open(output_dir + "/" + plugin + "/infected_" + plugin + ".txt") as f: 1219 | for line in f: 1220 | if re.search(r"[a-zA-Z\.]\s+%s " % pid, line, re.IGNORECASE): 1221 | report.write(line) 1222 | 1223 | # processing netscan output 1224 | elif plugin == "netscan": 1225 | report_plugin(plugin, 1) 1226 | 1227 | # filtering mutantscan output 1228 | elif plugin == "mutantscan": 1229 | 1230 | with open(output_dir + "/" + plugin + "/diff_" + plugin + ".txt") as diff_mutants: 1231 | mutants = [] 1232 | for line in diff_mutants: 1233 | mutant = ' '.join((re.sub(' +', ' ', line).split(' ')[5:])) 1234 | if mutant != '\n': 1235 | mutants.append(mutant) 1236 | mutants = sorted(set(mutants)) 1237 | if len(mutants) > 0: 1238 | report.write("\n\nNew %s entries." % plugin) 1239 | report.write( 1240 | "\n==========================================================================================================================\n") 1241 | for mutant in mutants: 1242 | report.write(mutant) 1243 | 1244 | # ensuring malfind output is completely reported 1245 | elif plugin == "malfind": 1246 | report_plugin(plugin, 0, 500) 1247 | 1248 | # processing plugins that don't need output formatting: 1249 | 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": 1250 | report_plugin(plugin) 1251 | 1252 | # processing other plugins: 1253 | else: 1254 | report_plugin(plugin, 2) 1255 | 1256 | # display list of plugins with no notable changes: 1257 | if len(no_new_entries) != 0: 1258 | report.write("\n\nNo notable changes to highlight from the following plugins.") 1259 | report.write( 1260 | "\n==========================================================================================================================\n") 1261 | for plugin in no_new_entries: 1262 | report.write(plugin + "\n") 1263 | 1264 | # display list of plugins hidden from report (verbose): 1265 | report.write("\n\nPlugins that were executed but are not included in the report above.") 1266 | report.write( 1267 | "\n==========================================================================================================================\n") 1268 | report.write( 1269 | "filescan\nhandles\ngetsids\ndeskscan\ndlllist\nldrmodules\natoms\nsvcscan\natomscan\nidt\ngdt\ntimers\ngditimers") 1270 | 1271 | # MALWARE CHECKS ================================================================ 1272 | if "--malware-checks" not in sys.argv: 1273 | if mode == "standalone": 1274 | try: 1275 | os.remove(output_dir + "/VolDiff_Report.txt") 1276 | except: 1277 | pass 1278 | script_completion(starttime) 1279 | 1280 | # PRINT BANNERS ================================================================ 1281 | print("\nHunting for malicious artifacts in memory...") 1282 | if mode == "dual": 1283 | report.write("\n\n") 1284 | report.write(" _ _ _ __ _ _ \n") 1285 | report.write(" /_\ _ __ __ _| |_ _ ___(_)___ /__\ ___ ___ _ _| | |_ ___ \n") 1286 | report.write(" //_\\\\| '_ \\ / _\`| | | | / __| / __| / \\/// _ \\/ __| | | | | __/ __|\n") 1287 | report.write("/ _ \\ | | | (_| | | |_| \\__ \\ \\__ \\ / _ \\ __/\\__ \\ |_| | | |_\\__ \\\n") 1288 | report.write("\_/ \_/_| |_|\__,_|_|\__, |___/_|___/ \/ \_/\___||___/\__,_|_|\__|___/\n") 1289 | report.write(" |___/ \n") 1290 | elif mode == "standalone": 1291 | report.write("\n") 1292 | report.write(" _ ___ _ __ __ _ _ _ __ _ _ \n") 1293 | report.write(" /\ /\___ | | / (_)/ _|/ _| /_\ _ __ __ _| |_ _ ___(_)___ /__\ ___ ___ _ _| | |_ ___ \n") 1294 | report.write(" \\ \\ / / _ \\| | / /\\ / | |_| |_ //_ \\| '_ \\ / _\`| | | | / __| / __| / \\/// _ \\/ __| | | | | __/ __|\n") 1295 | report.write(" \\ V / (_) | |/ /_//| | _| _| / _ \\ | | | (_| | | |_| \\__ \\ \\__ \\ / _ \\ __/\\__ \\ |_| | | |_\\__ \\\n") 1296 | report.write(" \_/ \___/|_/___,' |_|_| |_| \_/ \_/_| |_|\__,_|_|\__, |___/_|___/ \/ \_/\___||___/\__,_|_|\__|___/\n") 1297 | report.write(" |___/ \n") 1298 | report.write("\nVolatility analysis report of %s (%s)" % (memory_image, profile)) 1299 | report.write("\nReport created by VolDiff v" + version + " on the " + datetime.datetime.now().strftime("%d/%m/%Y %H:%M")) 1300 | report.write("\nDownload the latest VolDiff version from https://github.com/aim4r/VolDiff/") 1301 | 1302 | # PIDS FOR ANALYSIS ================================================================ 1303 | pids_to_analyse = {} 1304 | if mode == "standalone": 1305 | unusual_pids = get_all_pids(usual_processes) 1306 | for pid in unusual_pids: 1307 | if pid in pids_to_analyse: 1308 | pids_to_analyse[pid] += ", non-default process" 1309 | else: 1310 | pids_to_analyse[pid] = "non-default process" 1311 | malfind_pids = get_malfind_pids() 1312 | for pid in malfind_pids: 1313 | if pid in pids_to_analyse: 1314 | pids_to_analyse[pid] += ", potential code injection" 1315 | else: 1316 | pids_to_analyse[pid] = "potential code injection" 1317 | else: 1318 | unusual_pids = get_diff_pids("conhost.exe|ipconfig.exe|cmd.exe") 1319 | for pid in unusual_pids: 1320 | if pid in pids_to_analyse: 1321 | pids_to_analyse[pid] += ", new process" 1322 | else: 1323 | pids_to_analyse[pid] = "New process" 1324 | malfind_pids = get_malfind_pids() 1325 | for pid in malfind_pids: 1326 | if pid in pids_to_analyse: 1327 | pids_to_analyse[pid] += ", potential code injection" 1328 | else: 1329 | pids_to_analyse[pid] = "potential code injection" 1330 | 1331 | # MALWARE CHECKS - NETWORK ================================================================ 1332 | # compute unique IPs from netscan output: 1333 | report_anomalies("IP addresses found in netscan output.", find_ips_domains_emails("netscan")) 1334 | # compute unique IPs and domains from iehistory output: 1335 | report_anomalies("IP addresses, domains and emails found in iehistory output.", find_ips_domains_emails("iehistory")) 1336 | 1337 | # MALWARE CHECKS - PROCESS ANOMALIES ================================================================ 1338 | # verify PID of System process = 4 1339 | system_pids = get_pids("system") 1340 | system_process_check = False 1341 | for pid in system_pids: 1342 | if pid != '4': 1343 | system_process_check = True 1344 | if pid in pids_to_analyse: 1345 | pids_to_analyse[pid] += ", unusual pid (not 4)" 1346 | else: 1347 | pids_to_analyse[pid] = "unusual pid (not 4)" 1348 | if system_process_check: 1349 | l = get_associated_process_lines_pids(system_pids) 1350 | report_anomalies("Unusual system process PID (different to 4).", l, "=", "psscan", 2) 1351 | # verify that only one instance of certain processes is running: 1352 | for process in uniq_processes: 1353 | pids = get_pids(process) 1354 | if len(pids) > 1: 1355 | l = get_associated_process_lines_pids(get_pids(process)) 1356 | report_anomalies("Unexpected multiple instances of " + process + ".", l, "=", "psscan", 2) 1357 | # verify that some processes do not have a child: 1358 | nochild_processes = ["lsass.exe", "lsm.exe"] 1359 | for process in nochild_processes: 1360 | pids = get_pids(process) 1361 | childs = get_childs_of(pids) 1362 | if len(childs) > 0: 1363 | parent_lines = get_associated_process_lines_pids(get_pids(process)) 1364 | child_lines = get_associated_process_lines_pids(childs) 1365 | report_anomalies("Process " + process + " has unexpected childs.", parent_lines + child_lines, "=", "psscan", 2) 1366 | for pid in pids: 1367 | pidchilds = get_childs_of([pid, ]) 1368 | if len(pidchilds) > 0: 1369 | if pid in pids_to_analyse: 1370 | pids_to_analyse[pid] += ", has unexpected child process" 1371 | else: 1372 | pids_to_analyse[pid] = "has unexpected child process" 1373 | # verify child/parent process relationships: 1374 | for parent in parent_child: 1375 | for child in parent_child[parent]: 1376 | child_pids = get_pids(child) 1377 | for pid in child_pids: 1378 | parent_pids = get_parent_pids_of([pid, ]) 1379 | parent_procnames = get_procnames(parent_pids) 1380 | for parent_procname in parent_procnames: 1381 | if parent_procname.lower() != parent.lower(): 1382 | j = get_associated_process_lines_pids([pid, ]) 1383 | l = get_associated_process_lines_pids(parent_pids) 1384 | report_anomalies("Unexpected parent process of " + child + " PID " + pid + " (" + parent_procname + " instead of " + parent + ").", j + l, "=", "psscan", 2) 1385 | if pid in pids_to_analyse: 1386 | pids_to_analyse[pid] += ", has an unexpected parent process" 1387 | else: 1388 | pids_to_analyse[pid] = "has an unexpected parent process" 1389 | # verify that every process has a parent (except for explorer.exe, csrss.exe, wininit.exe and winlogon.exe) 1390 | pids = get_all_pids() 1391 | ppids = get_all_ppids("explorer.exe|csrss.exe|wininit.exe|winlogon.exe|system") 1392 | for ppid in ppids: 1393 | if ppid not in pids: 1394 | l = get_associated_process_lines_ppids([ppid, ]) 1395 | report_anomalies("Parent process with PPID " + ppid + " is not listed in psscan output.", l, "=", "psscan", 2) 1396 | # verify processes are running in expected sessions: 1397 | for process in session0_processes: 1398 | process_pids = get_pids(process) 1399 | for pid in process_pids: 1400 | session = get_session(pid) 1401 | if session != '0': 1402 | l = get_associated_process_lines_pids([pid, ], "pslist") 1403 | report_anomalies("Process " + process + " (" + str(pid) + ") is running in unexpected session (" + session + " instead of 0).", l, "=", "pslist", 2) 1404 | if pid in pids_to_analyse: 1405 | pids_to_analyse[pid] += ", running in an unusual session" 1406 | else: 1407 | pids_to_analyse[pid] = "running in an unusual session" 1408 | for process in session1_processes: 1409 | process_pids = get_pids(process) 1410 | for pid in process_pids: 1411 | session = get_session(pid) 1412 | if session != '1': 1413 | l = get_associated_process_lines_pids([pid, ], "pslist") 1414 | report_anomalies("Process " + process + " (" + str(pid) + ") is running in unexpected session (" + session + " instead of 1).", l, "=", "pslist", 2) 1415 | if pid in pids_to_analyse: 1416 | pids_to_analyse[pid] += ", running in an unusual session" 1417 | else: 1418 | pids_to_analyse[pid] = "running in an unusual session" 1419 | # check process executable path: 1420 | for process in process_execpath: 1421 | process_pids = get_pids(process) 1422 | for pid in process_pids: 1423 | path = get_execpath(pid) 1424 | correct_path = process_execpath[process] 1425 | if path != "" and path.lower() != correct_path.lower(): 1426 | l = get_associated_process_lines_pids([pid, ], "psscan") 1427 | report_anomalies("Process " + process + " (" + pid + ") is running from an unexpected path (" + path.lower() + " instead of " + correct_path.lower() + ").", l, "=", "psscan", 2) 1428 | if pid in pids_to_analyse: 1429 | pids_to_analyse[pid] += ", running from an unexpected execution path" 1430 | else: 1431 | pids_to_analyse[pid] = "running from an unexpected execution path" 1432 | # verify if any processes have suspicious l33t names: 1433 | leet_processes = anomaly_search("psscan", l33t_process_name, 'yes', '', "diff") 1434 | report_anomalies("Suspicious process name found.", leet_processes, "=", "psscan", 2) 1435 | for l in leet_processes: 1436 | pid = re.sub(' +', ' ', l).split(' ')[2] 1437 | if pid in pids_to_analyse: 1438 | pids_to_analyse[pid] += ", has a suspicious process name" 1439 | else: 1440 | pids_to_analyse[pid] = "has a suspicious process name" 1441 | # check if any process is running from a TEMP directory: 1442 | pid_list = get_all_pids() 1443 | for pid in pid_list: 1444 | path = get_execpath(pid) 1445 | process = get_procname(pid) 1446 | if re.search(temp_filepath, path, re.IGNORECASE): 1447 | l = get_associated_process_lines_pids([pid, ], "psscan") 1448 | report_anomalies("Process " + process + " PID " + pid + " is running from a temporary folder (" + path.lower() + ").", l, "=", "psscan", 2) 1449 | if pid in pids_to_analyse: 1450 | pids_to_analyse[pid] += ", running from a temporary folder" 1451 | else: 1452 | pids_to_analyse[pid] = "running from a temporary folder" 1453 | # verify if any hacker tools were used in process list: 1454 | hacker_processes = anomaly_search("psscan", hacker_process_regex, 'yes', '', "diff") 1455 | report_anomalies("Process(es) that may have been used for lateral movement, exfiltration etc.", hacker_processes, "=", "psscan", 2) 1456 | # detect process hollowing: 1457 | path = output_dir + "/procdump/" 1458 | dumped_process_filenames = os.listdir(path) 1459 | procnames = get_all_procnames() 1460 | for procnameh in procnames: 1461 | procpids = get_pids(procnameh) 1462 | report_string = "" 1463 | if len(procpids) > 1: 1464 | procname_sizes = [] 1465 | unique_procname_sizes = [] 1466 | for pid in procpids: 1467 | for dumped_process_filename in dumped_process_filenames: 1468 | if re.search("executable." + pid + ".exe", dumped_process_filename, re.IGNORECASE): 1469 | procname_sizes.append(os.stat(path + dumped_process_filename).st_size) 1470 | unique_procname_sizes = sorted(set(procname_sizes)) 1471 | report_string += procnameh + " " + pid + " " + str( 1472 | os.stat(path + dumped_process_filename).st_size) + "\n" 1473 | if len(unique_procname_sizes) > 1: 1474 | report.write("\n\nPotential process hollowing detected in " + procnameh + " (based on size).") 1475 | report.write( 1476 | "\n==========================================================================================================================\n") 1477 | report.write("\nProcess PID Size") 1478 | report.write("\n----------------------------\n") 1479 | report.write(report_string) 1480 | # detect processes with exit time but active threads: 1481 | report_anomalies("Process(es) with exit time and active threads.", deadproc_activethreads(), "=", "psxview", 2) 1482 | for d in deadproc_activethreads(): 1483 | pid = re.sub(' +', ' ', d).split(' ')[2] 1484 | if pid in pids_to_analyse: 1485 | pids_to_analyse[pid] += ", has an exit time and active threads" 1486 | else: 1487 | pids_to_analyse[pid] = "has an exit time and active threads" 1488 | # check if any process has domain or enterprise admin privileges: 1489 | high_privileges_regex = "Domain Admin|Enterprise Admin|Schema Admin" 1490 | high_privileges = anomaly_search("getsids", high_privileges_regex, 'yes', '', "diff") 1491 | report_anomalies("Process(es) with domain or enterprise admin privileges.", high_privileges) 1492 | # check if any process has debug privileges: 1493 | debug_privileges = anomaly_search("getsids", "debug", 'yes', '', "diff") 1494 | report_anomalies("Process(es) with debug privileges.", debug_privileges) 1495 | 1496 | # MALWARE CHECKS - SUSPICIOUS DLLs/EXEs ================================================================ 1497 | # Prefetch artifacts (mftparser): [DUAL ONLY] 1498 | if mode == "dual": 1499 | prefetch_files = anomaly_search("mftparser", ".pf$", 'yes') 1500 | prefetch_files_to_report = [] 1501 | for entry in prefetch_files: 1502 | pf = ' '.join((re.sub(' +', ' ', entry).split(' ')[12:])) 1503 | if pf != "": 1504 | prefetch_files_to_report.append(pf) 1505 | report_anomalies("Prefetch artifacts (mftparser).", prefetch_files_to_report) 1506 | # Suspicious dlls/executables (dlllist) - loaded from temp folders, unusual new (DUAL ONLY), etc: 1507 | temp_dlls = anomaly_search("dlllist", temp_filepath, 'yes') 1508 | temp_dlls = extract_substrings(temp_dlls, temp_filepath) 1509 | new_exe_excluded_regex = "system32|explorer.exe|iexplore.exe|VMware|wininit.exe|winlogon.exe|TrustedInstaller.exe|taskhost.exe|mscorsvw.exe|TPAutoConnect.exe|comctl32.dll" 1510 | new_exes = anomaly_search("dlllist", "Command line", 'yes', new_exe_excluded_regex) 1511 | if mode == "dual": 1512 | new_dlls = anomaly_search("dlllist", "C:.*.dll", 'yes', "System32") 1513 | new_dlls = extract_substrings(new_dlls, "C:.*.dll") 1514 | dlls = temp_dlls + new_exes + new_dlls 1515 | else: 1516 | dlls = temp_dlls + new_exes 1517 | dlls_to_report = [] 1518 | for dll in dlls: 1519 | b = re.sub('"', '', dll) 1520 | c = re.sub("Command line : ", "", b) 1521 | dlls_to_report.append(c) 1522 | dlls_to_report = tidy_list(dlls_to_report) 1523 | report_anomalies("Suspicious DLLs/EXEs (dlllist).", dlls_to_report) 1524 | # Hidden/suspicious DLLs/EXEs (ldrmodules): 1525 | 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" 1526 | hiddendlls1_ldrmodules = anomaly_search("ldrmodules", "False False False.*dll$|False False False.*exe$", 'yes', ldrmodules_excluded_regex_1) 1527 | ldrmodules_excluded_regex_2 = "system32|explorer.exe|iexplore.exe|.fon$|TrustedInstaller.exe|VMware\\\VMware Tools|mscorsvw.exe" 1528 | hiddendlls2_ldrmodules = anomaly_search("ldrmodules", "False", 'yes', ldrmodules_excluded_regex_2) 1529 | hiddendlls3_ldrmodules = anomaly_search("ldrmodules", "no name", 'yes') 1530 | hiddendlls_ldrmodules = hiddendlls1_ldrmodules + hiddendlls2_ldrmodules + hiddendlls3_ldrmodules 1531 | hiddendlls_ldrmodules = sorted(set(hiddendlls_ldrmodules)) 1532 | report_anomalies("Hidden/suspicious DLLs/EXEs (ldrmodules).", hiddendlls_ldrmodules, "=", "ldrmodules", 2) 1533 | # Suspicious DLLs (atoms): 1534 | dll_atoms = anomaly_search("atoms", ".dll$", 'yes', usual_atoms_dlls) 1535 | report_anomalies("Suspicious DLLs (atoms).", dll_atoms, "=", "atoms", 2) 1536 | # Suspicious DLLs (atomscan): 1537 | dll_atomscan = anomaly_search("atomscan", ".dll$", 'yes', usual_atoms_dlls) 1538 | report_anomalies("Suspicious DLLs (atomscan).", dll_atomscan, "=", "atomscan", 2) 1539 | # DLLs used for password theft or VM evasion (ldrmodules): 1540 | suspdll_ldrmodules = anomaly_search("ldrmodules", hacker_dll_regex, 'yes') 1541 | report_anomalies("DLLs used for password theft or VM evasion (ldrmodules).", suspdll_ldrmodules, "=", "ldrmodules", 2) 1542 | 1543 | # MALWARE CHECKS - SUSPICIOUS FILES ================================================================ 1544 | # Interesting files on disk (filescan) 1545 | if mode == "dual": 1546 | suspicious_files1 = anomaly_search("filescan", susp_filepath, 'yes', "\.db$|\.lnk$|\.ini$|\.log$", "diff") 1547 | suspicious_files2 = anomaly_search("filescan", susp_extensions_regex, 'yes', "suspend-vm-default\.bat") 1548 | suspicious_files = suspicious_files1 + suspicious_files2 1549 | else: 1550 | suspicious_files = anomaly_search("filescan", susp_extensions_regex, 'yes') 1551 | suspicious_files_to_report = [] 1552 | for entry in suspicious_files: 1553 | en = ' '.join((re.sub(' +', ' ', entry).split(' ')[4:])) 1554 | if en != "": 1555 | suspicious_files_to_report.append(en) 1556 | suspicious_files_to_report = sorted(set(suspicious_files_to_report)) 1557 | report_anomalies("Interesting files on disk (filescan).", suspicious_files_to_report, "=", "filescan", 0, 100) 1558 | # Alternate Data Stream (ADS) files (mftparser): 1559 | ads_files = anomaly_search("mftparser", "DATA ADS", 'yes', "Bad$|Max$") 1560 | report_anomalies("Alternate Data Stream (ADS) files (mftparser).", ads_files) 1561 | 1562 | # MALWARE CHECKS - MISC ================================================================ 1563 | # find suspicious desktop instances: [DUAL ONLY] 1564 | if mode == "dual": 1565 | new_desktops = anomaly_search("deskscan", "Desktop:", 'yes', '', 'diff') 1566 | report_anomalies("New desktop instances (deskscan).", new_desktops) 1567 | # find interesting entries in hosts file 1568 | if mode == "dual": 1569 | hostsb = get_hosts_contents(baseline_memory_image) 1570 | hostsi = get_hosts_contents(infected_memory_image) 1571 | hosts = [] 1572 | for line in hostsi: 1573 | if line not in hostsb: 1574 | hosts.append(line) 1575 | else: 1576 | hosts = get_hosts_contents(memory_image) 1577 | report_anomalies("Interesting 'hosts' file entries.", hosts) 1578 | 1579 | # MALWARE CHECKS - PERSISTENCE ================================================================ 1580 | # find new services: [Dual Only] 1581 | if mode == "dual": 1582 | services_to_report = filter_new_services() 1583 | report_anomalies("Notable new entries from svcscan.", services_to_report) 1584 | # highlight temp folders appearing in services: [Standalone Only] 1585 | if mode == "standalone": 1586 | temp_services = anomaly_search("svcscan", temp_filepath, 'yes') 1587 | report_anomalies("Temp folders appearing in svcscan output.", temp_services) 1588 | 1589 | # MALWARE CHECKS - KERNEL ================================================================ 1590 | # Keylogger traces (messagehooks): 1591 | keylogger_messagehooks = anomaly_search("messagehooks", "KEYBOARD", 'yes') 1592 | report_anomalies("Keylogger traces (messagehooks).", keylogger_messagehooks, "=", "messagehooks", 2) 1593 | # Unusual timers: 1594 | unusual_timers = anomaly_search_inverted("timers", usual_timers, 'yes') 1595 | if mode == "standalone": 1596 | report_anomalies("Unusual timers.", unusual_timers[2:], "=", "timers", 2) 1597 | else: 1598 | report_anomalies("Unusual timers.", unusual_timers, "=", "timers", 2) 1599 | # Suspicious 'unknown' timers: 1600 | unknown_timers = anomaly_search("timers", "UNKNOWN", 'yes') 1601 | report_anomalies("Suspicious 'unknown' timers.", unknown_timers, "=", "timers", 2) 1602 | # find unusual gditimers: 1603 | unusual_gditimers = anomaly_search_inverted("gditimers", usual_gditimers, 'yes') 1604 | if mode == "standalone": 1605 | report_anomalies("Unusual gditimers.", unusual_gditimers[2:], "=", "gditimers", 2, 20) 1606 | else: 1607 | report_anomalies("Unusual gditimers.", unusual_gditimers, "=", "gditimers", 2, 20) 1608 | # Suspicious 'unknown' callbacks: 1609 | unknown_callbacks = anomaly_search("callbacks", "UNKNOWN", 'yes') 1610 | report_anomalies("Suspicious 'unknown' callbacks.", unknown_callbacks, "=", "callbacks", 2) 1611 | # Suspicious 'unknown' drivermodules: 1612 | unknown_drivermodules = anomaly_search("drivermodule", "UNKNOWN", 'yes') 1613 | report_anomalies("Suspicious 'unknown' drivermodules.", unknown_drivermodules, "=", "drivermodule", 2, 20) 1614 | # Suspicious 'unknown' driverirp entries: 1615 | unknown_driverirp = anomaly_search("driverirp", "UNKNOWN", 'yes') 1616 | report_anomalies("Suspicious 'unknown' driverirp entries.", unknown_driverirp, "=", "", 0, 20) 1617 | # Unusual ssdt entries: 1618 | unusual_ssdt = anomaly_search_inverted("ssdt", usual_ssdt, 'yes', "Entry") 1619 | report_anomalies("Unusual ssdt entries.", unusual_ssdt, "=", "", 0, 20) 1620 | # Suspicious idt entries: 1621 | susp_idt = anomaly_search("idt", "rsrc", 'yes') 1622 | report_anomalies("Suspicious idt entries.", susp_idt, "=", "idt", 2) 1623 | # Suspicious orphan threads: 1624 | orphan_threads = anomaly_search("threads", ".*", 'yes') 1625 | if mode == "standalone": 1626 | report_anomalies("Suspicious orphan threads.", orphan_threads[2:]) 1627 | else: 1628 | report_anomalies("Suspicious orphan threads.", orphan_threads) 1629 | 1630 | # IMPSCAN AND PROCDUMP EXECUTION (IN PREPERATION FOR PROCESS PROFILER) ================================================================ 1631 | # run impscan plugin in // 1632 | impscanfolder = tmpfolder + "impscan/" 1633 | if not os.path.isdir(impscanfolder): 1634 | os.makedirs(impscanfolder) 1635 | else: 1636 | shutil.rmtree(impscanfolder) 1637 | os.makedirs(impscanfolder) 1638 | plugin = "impscan" 1639 | i = 0 1640 | subprocesses = {} 1641 | for pid in pids_to_analyse: 1642 | if mode == "dual": 1643 | with open(impscanfolder + str(pid) + ".txt", "w") as f: 1644 | subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, plugin, "--pid=" + str(pid)], stdout=f, stderr=devnull) 1645 | else: 1646 | with open(impscanfolder + str(pid) + ".txt", "w") as f: 1647 | subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, plugin, "--pid=" + str(pid)], stdout=f, stderr=devnull) 1648 | i += 1 1649 | if i >= max_concurrent_subprocesses: 1650 | for subproc in subprocesses: 1651 | subprocesses[subproc].wait() 1652 | i = 0 1653 | subprocesses = {} 1654 | for subproc in subprocesses: 1655 | subprocesses[subproc].wait() 1656 | 1657 | # dump suspicious processes to disk in // 1658 | subprocesses = {} 1659 | i = 0 1660 | for pid in pids_to_analyse: 1661 | procname = get_procname(pid) 1662 | dumpfolder = tmpfolder + str(pid) + "/" 1663 | if not os.path.isdir(dumpfolder): 1664 | os.makedirs(dumpfolder) 1665 | else: 1666 | shutil.rmtree(dumpfolder) 1667 | os.makedirs(dumpfolder) 1668 | offsets = [] 1669 | if procname != "": 1670 | f = open_full_plugin("psscan", 2) 1671 | for line in f: 1672 | if re.search(procname + " +" + str(pid) + " ", line, re.IGNORECASE): 1673 | offsets.append(re.sub(' +', ' ', line).split(' ')[0]) 1674 | f.close() 1675 | for offset in offsets: 1676 | if mode == "dual": 1677 | subprocesses[offset] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, "procdump", "--offset=" + offset, "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull) 1678 | else: 1679 | subprocesses[offset] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, "procdump", "--offset=" + offset, "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull) 1680 | i += 1 1681 | if i >= max_concurrent_subprocesses: 1682 | for subproc in subprocesses: 1683 | subprocesses[subproc].wait() 1684 | i = 0 1685 | subprocesses = {} 1686 | if mode == "dual": 1687 | subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", infected_memory_image, "malfind", "--pid=" + str(pid), "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull) 1688 | else: 1689 | subprocesses[str(pid)] = Popen([path_to_volatility, "--profile", profile, "-f", memory_image, "malfind", "--pid=" + str(pid), "--dump-dir=" + dumpfolder], stdout=devnull, stderr=devnull) 1690 | i += 1 1691 | if i >= max_concurrent_subprocesses: 1692 | for subproc in subprocesses: 1693 | subprocesses[subproc].wait() 1694 | i = 0 1695 | subprocesses = {} 1696 | for subproc in subprocesses: 1697 | subprocesses[subproc].wait() 1698 | 1699 | # DEV MODE SWITCH ================================================================ 1700 | if "--devmode" in sys.argv: 1701 | raw_input('\nCheckpoint before executing process profiler.') 1702 | 1703 | # MALWARE CHECKS - PROCESS PROFILER ================================================================ 1704 | # dispay list of processes that will be analysed 1705 | if len(pids_to_analyse) > 0: 1706 | report.write("\n\nProcesses that will be analysed in the next section:") 1707 | report.write( 1708 | "\n==========================================================================================================================\n") 1709 | for pid in pids_to_analyse: 1710 | report.write(get_procname(pid) + " (" + str(pid) + "): " + pids_to_analyse[pid] + ".\n") 1711 | l = get_associated_process_lines_pids(pids_to_analyse, "psscan") 1712 | report_anomalies("Psscan output for suspicious processes.", l, "-", "psscan", 2) 1713 | 1714 | for pid in pids_to_analyse: 1715 | procname = get_procname(pid) 1716 | report.write("\n\nAnalysis results for " + procname + " PID " + pid + " (" + pids_to_analyse[pid] + "):") 1717 | report.write( 1718 | "\n==========================================================================================================================") 1719 | # print VirusTotal scan results of exec MD5 hash 1720 | if vt_api_key != "": 1721 | susp_md5 = get_md5(pid) 1722 | if susp_md5 != "": 1723 | report_virustotal_md5_results(susp_md5, vt_api_key) 1724 | # print psxview output for the process (psxview) 1725 | l = get_associated_process_lines_pids([pid, ], "psxview") 1726 | report_anomalies("Psxview results:", l, '-', "psxview", 2) 1727 | # print comand line (cmdline) 1728 | cmdline = get_cmdline(pid) 1729 | report_anomalies("Command line (cmdline):", cmdline, '-') 1730 | # analyse network connections (netscan) 1731 | susp_connections = anomaly_search("netscan", " " + str(pid), "yes", "", "diff") 1732 | report_anomalies("Network connections (netscan):", susp_connections, '-', "netscan", 1) 1733 | # print parent process information 1734 | if len(get_parent_pids_of([pid, ])) > 0: 1735 | ppid = get_parent_pids_of([pid, ])[0] 1736 | pids = get_all_pids() 1737 | if ppid not in pids: 1738 | l = get_associated_process_lines_ppids([ppid, ]) 1739 | report_anomalies("Parent process (PPID " + ppid + ") is not listed in psscan output:", l, '-', "psscan", 1740 | 2) 1741 | else: 1742 | l = get_associated_process_lines_pids([ppid, ]) 1743 | j = get_associated_process_lines_pids([pid, ]) 1744 | report_anomalies("Parent process (PPID " + ppid + ") information:", j + l, '-', "psscan", 2) 1745 | # check if process has an "expected" parent 1746 | check_expected_parent(pid) 1747 | # print child process information 1748 | childs = get_childs_of([pid, ]) 1749 | l = get_associated_process_lines_pids(childs) 1750 | report_anomalies("Child process(es):", l, '-', "psscan", 2) 1751 | # print malfind injections (malfind) 1752 | malfind_injections = get_malfind_injections(pid, mode) 1753 | report_anomalies("Code injection (malfind):", malfind_injections, '-') 1754 | # print associated services (svcscan) 1755 | susp_services = get_associated_services(pid) 1756 | report_anomalies("Associated service(s) (svcscan):", susp_services, '-') 1757 | # print envars (envars) 1758 | associated_envars = anomaly_search("envars", " " + str(pid) + " ", "yes", "", "diff") 1759 | report_anomalies("Environment variables (envars):", associated_envars, '-', "envars", 2) 1760 | # print interesting DLLs (ldrmodules) 1761 | dlls1 = anomaly_search("ldrmodules", hacker_dll_regex, 'yes', "", "diff") 1762 | dlls2 = anomaly_search("ldrmodules", "no name", 'yes', "", "diff") 1763 | dlls3 = anomaly_search("ldrmodules", "False False False.*dll$|False False False.*exe$", 'yes', ldrmodules_excluded_regex_1, "diff") 1764 | dlls4 = anomaly_search("ldrmodules", "False", 'yes', ldrmodules_excluded_regex_2, "diff") 1765 | dlls = sorted(set(dlls1 + dlls2 + dlls3 + dlls4)) 1766 | dlls_to_report = [] 1767 | for dll in dlls: 1768 | if re.search(" " + str(pid) + " ", dll, re.IGNORECASE): 1769 | dlls_to_report.append(dll) 1770 | report_anomalies("Interesting DLLs (ldrmodules):", dlls_to_report, '-', "ldrmodules", 2) 1771 | # print mutants (handles) DUAL ONLY 1772 | if mode == "dual": 1773 | mutants = anomaly_search("handles", " " + str(pid) + " .*Mutant", "yes", "", "diff") 1774 | report_anomalies("Mutants accessed (handles):", mutants, '-', "handles", 2) 1775 | # print interesting files accessed (handles) 1776 | files1 = anomaly_search("handles", " " + str(pid) + " .*\\\Device\\\RawIp", 'yes', '', "diff") 1777 | files2 = anomaly_search("handles", " " + str(pid) + " .*Device\\\(LanmanRedirector|Mup)", 'yes', '', "diff") 1778 | files3 = anomaly_search("handles", " " + str(pid) + " .*\..{2,3}$", 'yes', "\.mui$", "diff") 1779 | files_to_report = [] 1780 | filelist = sorted(set(files1 + files2 + files3)) 1781 | for i in filelist: 1782 | if re.search("File", i, re.IGNORECASE): 1783 | files_to_report.append(i) 1784 | report_anomalies("Interesting files accessed (handles):", files_to_report, '-', "handles", 2) 1785 | # print privileges (privs) 1786 | privs = anomaly_search("privs", " " + str(pid) + " ", "yes", "", "diff") 1787 | report_anomalies("Enabled privileges (privs):", privs, '-', "privs", 2) 1788 | # print high privileges (getsids) 1789 | sids = anomaly_search("getsids", "\(" + str(pid) + "\).*(Domain Admin|Enterprise Admin|Schema Admin)", "yes", "", "diff") 1790 | report_anomalies("Process privileges (getsids):", sids, '-') 1791 | # check if process has a raw socket handle: 1792 | raw_sockets = get_raw_sockets(pid) 1793 | report_anomalies("Raw socket handles:", raw_sockets, "-", "handles", 2) 1794 | # check if process has a handle to a remote mapped share: 1795 | share_handles = get_remote_share_handles(pid) 1796 | report_anomalies("Remote share handles:", share_handles, "-", "handles", 2) 1797 | # print handles to interesting registry entries (handles) 1798 | if get_procname(pid) != 'explorer.exe': 1799 | analyse_registry(pid) 1800 | # print interesting imports (impscan) 1801 | if get_procname(pid) != 'explorer.exe': 1802 | analyse_imports(pid) 1803 | # find suspicious strings in process strings (procdump + malfind + strings) 1804 | analyse_strings(pid) 1805 | 1806 | # CLEANUP AND CLOSURE ================================================================ 1807 | script_completion(starttime) 1808 | 1809 | 1810 | if __name__ == '__main__': 1811 | main() 1812 | --------------------------------------------------------------------------------