Advertisement
opexxx

Noriben.py

May 2nd, 2014
292
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 39.05 KB | None | 0 0
  1. # Noriben Malware Analysis Sandbox
  2. #
  3. # Directions:
  4. # Just copy Noriben.py to a Windows-based VM alongside the Sysinternals Procmon.exe
  5. #
  6. # Run Noriben.py, then run your executable.
  7. # When the executable has completed its processing, stop Noriben and you'll have a clean text report and timeline
  8. #
  9. # Version 1.0 - 10 Apr 13 - @bbaskin - brian@thebaskins.com
  10. #       Gracious edits, revisions, and corrections by Daniel Raygoza
  11. # Version 1.1 - 21 Apr 13 -
  12. #       Much improved filters and filter parsing
  13. # Version 1.1a - 1 May 13 -
  14. #       Revamped regular expression support. Added Python 3.x forward
  15. #       compatibility
  16. # Version 1.2 - 28 May 13 -
  17. #       Now reads CSV files line-by-line to handle large files, keep
  18. #       unsuccessful registry deletes, compartmentalize sections, creates CSV
  19. #       timeline, can reparse PMLs, can specify alternative PMC filters,
  20. #       changed command line arguments, added global blacklist
  21. # Version 1.3 - 13 Sep 13 -
  22. #       Option to generalize file paths in output, option to use a timeout
  23. #       instead of Ctrl-C to end monitoring, only writes RegSetValue entries
  24. #       if Length > 0
  25. # Version 1.4 - 16 Sep 13 -
  26. #       Fixed string generalization on file rename and now supports ()'s in
  27. #       environment name (for 64-bit systems), added ability to Ctrl-C from
  28. #       a timeout, added specifying malware file from command line, added an
  29. #       output directory
  30. # Version 1.5 - 28 Sep 13 -
  31. #       Standardized to single quotes, added YARA scanning of resident files,
  32. #       reformatted function comments to match appropriate docstring format,
  33. #       fixed bug with generalize paths - now generalizes after getting MD5
  34. # Version 1.5b - 1 Oct 13 -
  35. #       Ninja edits to fix a few small bug fixes and change path generalization
  36. #       to an ordered list instead of an unordered dictionary. This lets you
  37. #       prioritize resolutions.
  38. #
  39. # TODO:
  40. # * extract data directly from registry? (may require python-registry - http://www.williballenthin.com/registry/)
  41. # * scan for mutexes, preferably in a way that doesn't require wmi/pywin32
  42.  
  43. from __future__ import print_function
  44. import codecs
  45. import fileinput
  46. import hashlib
  47. import os
  48. import re
  49. import subprocess
  50. import sys
  51. from argparse import ArgumentParser
  52. from datetime import datetime
  53. from string import whitespace
  54. from time import sleep
  55. from traceback import format_exc
  56.  
  57. try:
  58.     import yara
  59.     has_yara = True
  60. except ImportError:
  61.     has_yara = False
  62.  
  63. # The below are customizable variables. Change these as you see fit.
  64. procmon = 'procmon.exe'  # Change this if you have a renamed procmon.exe
  65. generalize_paths = False  # generalize paths to their base environment variable
  66. enable_timeline = True
  67. use_pmc = False
  68. debug = False
  69. timeout_seconds = 0  # Set to 0 to manually end monitoring with Ctrl-C
  70.  
  71. # The below is for global internal variables. Don't touch them.
  72. __VERSION__ = '1.5'
  73. path_general_list = []
  74. yara_folder = ''
  75.  
  76. # Rules for creating rules:
  77. # 1. Every rule string must begin with the `r` for regular expressions to work.
  78. # 1.a. This signifies a 'raw' string.
  79. # 2. No backslashes at the end of a filter. Either:
  80. # 2.a. truncate the backslash, or
  81. # 2.b. use '\*' to signify 'zero or more slashes'.
  82. # 3. To find a list of available '%%' variables, type `set` from a command prompt
  83.  
  84. # These entries are applied to all blacklists
  85. global_blacklist = [r'VMwareUser.exe',
  86.                     r'CaptureBAT.exe',
  87.                     r'SearchIndexer.exe',
  88.                     r'Fakenet.exe',
  89.                     r'idaq.exe']
  90.  
  91. cmd_blacklist = [r'%SystemRoot%\system32\wbem\wmiprvse.exe',
  92.                  r'%SystemRoot%\system32\wscntfy.exe',
  93.                  r'procmon.exe',
  94.                  r'wuauclt.exe',
  95.                  r'jqs.exe',
  96.                  r'TCPView.exe'] + global_blacklist
  97.  
  98. file_blacklist = [r'procmon.exe',
  99.                   r'Desired Access: Execute/Traverse',
  100.                   r'Desired Access: Synchronize',
  101.                   r'Desired Access: Generic Read/Execute',
  102.                   r'Desired Access: Read EA',
  103.                   r'Desired Access: Read Data/List',
  104.                   r'Desired Access: Generic Read, ',
  105.                   r'Desired Access: Read Attributes',
  106.                   r'Google\Chrome\User Data\.*.tmp',
  107.                   r'wuauclt.exe',
  108.                   r'wmiprvse.exe',
  109.                   r'Microsoft\Windows\Explorer\thumbcache_.*.db',
  110.                   r'Thumbs.db$',
  111.  
  112.                   r'%AllUsersProfile%\Application Data\Microsoft\OFFICE\DATA',
  113.                   r'%AppData%\Microsoft\Proof\*',
  114.                   r'%AppData%\Microsoft\Templates\*',
  115.                   r'%LocalAppData%\Google\Drive\sync_config.db*',
  116.                   r'%ProgramFiles%\Capture\*',
  117.                   r'%SystemDrive%\Python',
  118.                   r'%SystemRoot%\assembly',
  119.                   r'%SystemRoot%\Prefetch\*',
  120.                   r'%SystemRoot%\system32\wbem\Logs\*',
  121.                   r'%UserProfile%$',
  122.                   r'%UserProfile%\AppData\LocalLow$',
  123.                   r'%UserProfile%\Recent\*',
  124.                   r'%UserProfile%\Local Settings\History\History.IE5\*'] + global_blacklist
  125.  
  126. reg_blacklist = [r'CaptureProcessMonitor',
  127.                  r'consent.exe',
  128.                  r'procmon.exe',
  129.                  r'verclsid.exe',
  130.                  r'wmiprvse.exe',
  131.                  r'wscntfy.exe',
  132.                  r'wuauclt.exe',
  133.  
  134.                  r'HKCR$',
  135.                  r'HKCR\AllFilesystemObjects\shell',
  136.  
  137.                  r'HKCU$',
  138.                  r'HKCU\Printers\DevModePerUser',
  139.                  r'HKCU\SessionInformation\ProgramCount',
  140.                  r'HKCU\Software$',
  141.                  r'HKCU\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\SideBySide',
  142.                  r'HKCU\Software\Classes\Local Settings\MuiCache\*',
  143.                  r'HKCU\Software\Microsoft\Calc$',
  144.                  r'HKCU\Software\Microsoft\.*\Window_Placement',
  145.                  r'HKCU\Software\Microsoft\Internet Explorer\TypedURLs',
  146.                  r'HKCU\Software\Microsoft\Notepad',
  147.                  r'HKCU\Software\Microsoft\Office',
  148.                  r'HKCU\Software\Microsoft\Shared Tools',
  149.                  r'HKCU\Software\Microsoft\SystemCertificates\Root$',
  150.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Applets',
  151.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CIDOpen',
  152.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Modules',
  153.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2',
  154.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU',
  155.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\SessionInfo',
  156.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage',
  157.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartPage2',
  158.                  r'HKCU\Software\Microsoft\Windows\Currentversion\Explorer\StreamMRU',
  159.                  r'HKCU\Software\Microsoft\Windows\Currentversion\Explorer\Streams',
  160.                  r'HKCU\Software\Microsoft\Windows\CurrentVersion\Group Policy',
  161.                  r'HKCU\Software\Microsoft\Windows\Shell',
  162.                  r'HKCU\Software\Microsoft\Windows\Shell\BagMRU',
  163.                  r'HKCU\Software\Microsoft\Windows\Shell\Bags',
  164.                  r'HKCU\Software\Microsoft\Windows\ShellNoRoam\MUICache',
  165.                  r'HKCU\Software\Microsoft\Windows\ShellNoRoam\BagMRU',
  166.                  r'HKCU\Software\Microsoft\Windows\ShellNoRoam\Bags',
  167.                  r'HKCU\Software\Policies$',
  168.                  r'HKCU\Software\Policies\Microsoft$',
  169.  
  170.                  r'HKLM$',
  171.                  r'HKLM\SOFTWARE$',
  172.                  r'HKLM\SOFTWARE\Microsoft$',
  173.                  r'HKLM\SOFTWARE\Policies$',
  174.                  r'HKLM\SOFTWARE\Policies\Microsoft$',
  175.                  r'HKLM\SOFTWARE\MICROSOFT\SystemCertificates$',
  176.                  r'HKLM\Software\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products',
  177.                  r'HKLM\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Cache\Paths\*',
  178.                  r'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render',
  179.                  r'HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions',
  180.                  r'HKLM\Software\Microsoft\WBEM',
  181.                  r'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Prefetcher\*',
  182.                  r'HKLM\Software\Microsoft\Windows NT\CurrentVersion\Tracing\*',
  183.                  r'HKLM\System\CurrentControlSet\Control\CLASS\{4D36E968-E325-11CE-BFC1-08002BE10318}',
  184.                  r'HKLM\System\CurrentControlSet\Control\DeviceClasses',
  185.                  r'HKLM\System\CurrentControlSet\Control\MediaProperties',
  186.                  r'HKLM\System\CurrentControlSet\Enum\*',
  187.                  r'HKLM\System\CurrentControlSet\Services\CaptureRegistryMonitor',
  188.                  r'HKLM\System\CurrentControlSet\Services\Eventlog\*',
  189.                  r'HKLM\System\CurrentControlSet\Services\Tcpip\Parameters',
  190.                  r'HKLM\System\CurrentControlSet\Services\WinSock2\Parameters',
  191.  
  192.                  r'LEGACY_CAPTUREREGISTRYMONITOR',
  193.                  r'Software\Microsoft\Multimedia\Audio$',
  194.                  r'Software\Microsoft\Multimedia\Audio Compression Manager',
  195.                  r'Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder',
  196.                  r'Software\Microsoft\Windows\ShellNoRoam\Bags',
  197.                  r'Software\Microsoft\Windows\ShellNoRoam\BagMRU',
  198.                  r'Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.doc',
  199.                  r'Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs',
  200.                  r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders',
  201.                  r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders',
  202.                  r'UserAssist\{5E6AB780-7743-11CF-A12B-00AA004AE837}',
  203.                  r'UserAssist\{75048700-EF1F-11D0-9888-006097DEACF9}',
  204.                  r'UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}'] + global_blacklist
  205.  
  206. net_blacklist = [r'hasplms.exe'] + global_blacklist  # Hasp dongle beacons
  207.                  #r'192.168.2.',                     # Particular to my network
  208.                  #r'Verizon_router.home']            # Particular to my network
  209.  
  210.  
  211. def generalize_vars_init():
  212.     """
  213.    Initialize a dictionary with the local system's environment variables.
  214.    Returns via a global variable, path_general_list
  215.  
  216.    Arguments:
  217.        none
  218.    Results:
  219.        none
  220.    """
  221.     envvar_list = [r'%AllUsersProfile%',
  222.                    r'%LocalAppData%',
  223.                    r'%AppData%',
  224.                    r'%CommonProgramFiles%',
  225.                    r'%ProgramData%',
  226.                    r'%ProgramFiles%',
  227.                    r'%ProgramFiles(x86)%',
  228.                    r'%Public%',
  229.                    r'%Temp%',
  230.                    r'%UserProfile%',
  231.                    r'%WinDir%']
  232.  
  233.     global path_general_list
  234.     print('[*] Enabling Windows string generalization.')
  235.     for env in envvar_list:
  236.         resolved = os.path.expandvars(env).encode('unicode_escape')
  237.         resolved = resolved.replace('(', '\\(').replace(')', '\\)')
  238.         if not resolved == env and not resolved == env.replace('(', '\\(').replace(')', '\\)'):
  239.             path_general_list.append([env, resolved])
  240.  
  241.  
  242. def generalize_var(path_string):
  243.     """
  244.    Generalize a given string to include its environment variable
  245.  
  246.    Arguments:
  247.        path_string: string value to generalize
  248.    Results:
  249.        string value of a generalized string
  250.    """
  251.     if not len(path_general_list):
  252.         generalize_vars_init()  # Maybe you imported Noriben and forgot to call generalize_vars_init? No biggie.
  253.     for item in path_general_list:
  254.         path_string = re.sub(item[1], item[0], path_string)
  255.     return path_string
  256.  
  257.  
  258. def yara_rule_check(yara_folder):
  259.     """
  260.    Scan a folder of YARA rule files to determine which provide syntax errors
  261.  
  262.    Arguments:
  263.        yara_folder: path to folder containing rules
  264.    """
  265.     for name in os.listdir(yara_folder):
  266.         fname = yara_folder + name
  267.         try:
  268.             rules = yara.compile(filepath=fname)
  269.         except yara.SyntaxError:
  270.             print('[!] YARA Syntax Error in file: %s' % fname)
  271.             print(format_exc())
  272.  
  273.  
  274. def yara_import_rules(yara_folder):
  275.     """
  276.    Import a folder of YARA rule files
  277.  
  278.    Arguments:
  279.        yara_folder: path to folder containing rules
  280.    Results:
  281.        rules: a yara.Rules structure of available YARA rules
  282.    """
  283.     yara_files = {}
  284.     if not yara_folder[-1] == '\\':
  285.         yara_folder += '\\'
  286.     print('[*] Loading YARA rules from folder: %s' % yara_folder)
  287.     files = os.listdir(yara_folder)
  288.     for file_name in files:
  289.         if '.yara' in file_name:
  290.             yara_files[file_name.split('.yara')[0]] = yara_folder + file_name
  291.     try:
  292.         rules = yara.compile(filepaths=yara_files)
  293.         print('[*] YARA rules loaded. Total files imported: %d' % len(yara_files))
  294.     except yara.SyntaxError:
  295.         print('[!] Syntax error found in one of the imported YARA files. Error shown below.')
  296.         rules = ''
  297.         yara_rule_check(yara_folder)
  298.         print('[!] YARA rules disabled until all Syntax Errors are fixed.')
  299.     return rules
  300.  
  301.  
  302. def yara_filescan(file_path, rules):
  303.     """
  304.    Scan a given file to see if it matches a given set of YARA rules
  305.  
  306.    Arguments:
  307.        file_path: full path to a file to scan
  308.        rules: a yara.Rules structure of available YARA rules
  309.    Results:
  310.        results: a string value that's either null (no hits)
  311.                 or formatted with hit results
  312.    """
  313.     if not rules:
  314.         return ''
  315.     try:
  316.         matches = rules.match(file_path)
  317.     except yara.Error:  # If can't open file
  318.         return ''
  319.     if matches:
  320.         results = '\t[YARA: %s]' % \
  321.                   reduce(lambda x, y: str(x) + ', ' + str(y), matches)
  322.     else:
  323.         results = ''
  324.     return results
  325.  
  326.  
  327. def open_file_with_assoc(fname):
  328.     """
  329.    Opens the specified file with its associated application
  330.  
  331.    Arguments:
  332.        fname: full path to a file to open
  333.    Results:
  334.        None
  335.    """
  336.     if os.name == 'mac':
  337.         subprocess.call(('open', fname))
  338.     elif os.name == 'nt':
  339.         os.startfile(fname)
  340.     elif os.name == 'posix':
  341.         subprocess.call(('open', fname))
  342.  
  343.  
  344. def file_exists(fname):
  345.     """
  346.    Determine if a file exists
  347.  
  348.    Arguments:
  349.        fname: path to a file
  350.    Results:
  351.        boolean value if file exists
  352.    """
  353.     return os.path.exists(fname) and os.access(fname, os.X_OK)
  354.  
  355.  
  356. def check_procmon():
  357.     """
  358.    Finds the local path to Procmon
  359.  
  360.    Arguments:
  361.        None
  362.    Results:
  363.        folder path to procmon executable
  364.    """
  365.     global procmon
  366.  
  367.     if file_exists(procmon):
  368.         return procmon
  369.     else:
  370.         for path in os.environ['PATH'].split(os.pathsep):
  371.             if file_exists(os.path.join(path.strip('"'), procmon)):
  372.                 return os.path.join(path, procmon)
  373.  
  374.  
  375. def md5_file(fname):
  376.     """
  377.    Given a filename, returns the hex MD5 value
  378.  
  379.    Arguments:
  380.        fname: path to a file
  381.    Results:
  382.        hex MD5 value of file's contents as a string
  383.    """
  384.     return hashlib.md5(codecs.open(fname, 'rb').read()).hexdigest()
  385.  
  386.  
  387. def get_session_name():
  388.     """
  389.    Returns current date and time stamp for file name
  390.  
  391.    Arguments:
  392.        None
  393.    Results:
  394.        string value of a current timestamp to apply to log file names
  395.    """
  396.     return datetime.now().strftime('%d_%b_%y__%H_%M_%S_%f')
  397.  
  398.  
  399. def protocol_replace(text):
  400.     """
  401.    Replaces text name resolutions from domain names
  402.  
  403.    Arguments:
  404.        text: string of domain with resolved port name
  405.    Results:
  406.        string value with resolved port name in decimal format
  407.    """
  408.     replacements = [(':https', ':443'),
  409.                     (':http', ':80'),
  410.                     (':domain', ':53')]
  411.     for find, replace in replacements:
  412.         text = text.replace(find, replace)
  413.     return text
  414.  
  415.  
  416. def blacklist_scan(blacklist, data):
  417.     """
  418.    Given a blacklist and data string, see if data is in blacklist
  419.  
  420.    Arguments:
  421.        blacklist: list of black-listed items
  422.        data: string value to compare against blacklist
  423.    Results:
  424.        boolean value of if item exists in blacklist
  425.    """
  426.     for event in data:
  427.         for bad in blacklist:
  428.             bad = os.path.expandvars(bad).replace('\\', '\\\\')
  429.             try:
  430.                 if re.search(bad, event, flags=re.IGNORECASE):
  431.                     return True
  432.             except re.error:
  433.                 print('Error found while processing filters.\r\nFilter:\t%s\r\nEvent:\t%s' % (bad, event))
  434.                 sys.stderr.write(format_exc())
  435.                 return False
  436.     return False
  437.  
  438.  
  439. def process_PML_to_CSV(procmonexe, pml_file, pmc_file, csv_file):
  440.     """
  441.    Uses Procmon to convert the PML to a CSV file
  442.  
  443.    Arguments:
  444.        procmonexe: path to Procmon executable
  445.        pml_file: path to Procmon PML output file
  446.        pmc_file: path to PMC filter file
  447.        csv_file: path to output CSV file
  448.    Results:
  449.        None
  450.    """
  451.     print('[*] Converting session to CSV: %s' % csv_file)
  452.     cmdline = '%s /OpenLog %s /saveas %s' % (procmonexe, pml_file, csv_file)
  453.     if use_pmc:
  454.         cmdline += ' /LoadConfig %s' % pmc_file
  455.     stdnull = subprocess.Popen(cmdline)
  456.     stdnull.wait()
  457.  
  458.  
  459. def launch_procmon_capture(procmonexe, pml_file, pmc_file):
  460.     """
  461.    Launch Procmon to begin capturing data
  462.  
  463.    Arguments:
  464.        procmonexe: path to Procmon executable
  465.        pml_file: path to Procmon PML output file
  466.        pmc_file: path to PMC filter file
  467.    Results:
  468.        None
  469.    """
  470.     cmdline = '%s /BackingFile %s /Quiet /Minimized' % (procmonexe, pml_file)
  471.     if use_pmc:
  472.         cmdline += ' /LoadConfig %s' % pmc_file
  473.     subprocess.Popen(cmdline)
  474.     sleep(3)
  475.  
  476.  
  477. def terminate_procmon(procmonexe):
  478.     """
  479.    Terminate Procmon cleanly
  480.  
  481.    Arguments:
  482.        procmonexe: path to Procmon executable
  483.    Results:
  484.        None
  485.    """
  486.     cmdline = '%s /Terminate' % procmonexe
  487.     stdnull = subprocess.Popen(cmdline)
  488.     stdnull.wait()
  489.  
  490.  
  491. def parse_csv(csv_file, report, timeline):
  492.     """
  493.    Given the location of CSV and TXT files, parse the CSV for notable items
  494.  
  495.    Arguments:
  496.        csv_file: path to csv output to parse
  497.    Results:
  498.        report: string text containing the entirety of the text report
  499.        timeline: string text containing the entirety of the CSV report
  500.    """
  501.     process_output = list()
  502.     file_output = list()
  503.     reg_output = list()
  504.     net_output = list()
  505.     error_output = list()
  506.     remote_servers = list()
  507.     if yara_folder and has_yara:
  508.         yara_rules = yara_import_rules(yara_folder)
  509.     else:
  510.         yara_rules = ''
  511.  
  512.     # Use fileinput.input() now to read data line-by-line
  513.     for original_line in fileinput.input(csv_file, openhook=fileinput.hook_encoded('iso-8859-1')):
  514.         server = ''
  515.         if original_line[0] != '"':  # Ignore lines that begin with Tab. Sysinternals breaks CSV with new processes
  516.             continue
  517.         line = original_line.strip(whitespace + '"')
  518.         field = line.strip().split('","')
  519.         try:
  520.             if field[3] in ['Process Create'] and field[5] == 'SUCCESS':
  521.                 cmdline = field[6].split('Command line: ')[1]
  522.                 if not blacklist_scan(cmd_blacklist, field):
  523.                     if generalize_paths:
  524.                         cmdline = generalize_var(cmdline)
  525.                     child_pid = field[6].split('PID: ')[1].split(',')[0]
  526.                     outputtext = '[CreateProcess] %s:%s > "%s"\t[Child PID: %s]' % (
  527.                         field[1], field[2], cmdline.replace('"', ''), child_pid)
  528.                     timelinetext = '%s,Process,CreateProcess,%s,%s,%s,%s' % (field[0].split()[0].split('.')[0],
  529.                                                                              field[1], field[2],
  530.                                                                              cmdline.replace('"', ''), child_pid)
  531.                     process_output.append(outputtext)
  532.                     timeline.append(timelinetext)
  533.  
  534.             elif field[3] == 'CreateFile' and field[5] == 'SUCCESS':
  535.                 if not blacklist_scan(file_blacklist, field):
  536.                     path = field[4]
  537.                     yara_hits = ''
  538.                     if yara_folder and yara_rules:
  539.                         yara_hits = yara_filescan(path, yara_rules)
  540.                     if os.path.isdir(path):
  541.                         if generalize_paths:
  542.                             path = generalize_var(path)
  543.                         outputtext = '[CreateFolder] %s:%s > %s' % (field[1], field[2], path)
  544.                         timelinetext = '%s,File,CreateFolder,%s,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  545.                                                                           field[2], path)
  546.                         file_output.append(outputtext)
  547.                         timeline.append(timelinetext)
  548.                     else:
  549.                         try:
  550.                             md5 = md5_file(path)
  551.                             if generalize_paths:
  552.                                 path = generalize_var(path)
  553.                             outputtext = '[CreateFile] %s:%s > %s\t[MD5: %s]%s' % (field[1], field[2], path, md5,
  554.                                                                                    yara_hits)
  555.                             timelinetext = '%s,File,CreateFile,%s,%s,%s,%s' % (field[0].split()[0].split('.')[0],
  556.                                                                                field[1], field[2], path, md5)
  557.                             file_output.append(outputtext)
  558.                             timeline.append(timelinetext)
  559.                         except (IndexError, IOError):
  560.                             if generalize_paths:
  561.                                 path = generalize_var(path)
  562.                             outputtext = '[CreateFile] %s:%s > %s\t[File no longer exists]' % (field[1], field[2], path)
  563.                             timelinetext = '%s,File,CreateFile,%s,%s,%s,N/A' % (field[0].split()[0].split('.')[0],
  564.                                                                                 field[1], field[2], path)
  565.                             file_output.append(outputtext)
  566.                             timeline.append(timelinetext)
  567.  
  568.             elif field[3] == 'SetDispositionInformationFile' and field[5] == 'SUCCESS':
  569.                 if not blacklist_scan(file_blacklist, field):
  570.                     path = field[4]
  571.                     if generalize_paths:
  572.                         path = generalize_var(path)
  573.                     outputtext = '[DeleteFile] %s:%s > %s' % (field[1], field[2], path)
  574.                     timelinetext = '%s,File,DeleteFile,%s,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  575.                                                                     field[2], path)
  576.                     file_output.append(outputtext)
  577.                     timeline.append(timelinetext)
  578.  
  579.             elif field[3] == 'SetRenameInformationFile':
  580.                 if not blacklist_scan(file_blacklist, field):
  581.                     from_file = field[4]
  582.                     to_file = field[6].split('FileName: ')[1].strip('"')
  583.                     if generalize_paths:
  584.                         from_file = generalize_var(from_file)
  585.                         to_file = generalize_var(to_file)
  586.                     outputtext = '[RenameFile] %s:%s > %s => %s' % (field[1], field[2], from_file, to_file)
  587.                     timelinetext = '%s,File,RenameFile,%s,%s,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  588.                                                                        field[2], from_file, to_file)
  589.                     file_output.append(outputtext)
  590.                     timeline.append(timelinetext)
  591.  
  592.             elif field[3] == 'RegCreateKey' and field[5] == 'SUCCESS':
  593.                 if not blacklist_scan(reg_blacklist, field):
  594.                     outputtext = '[RegCreateKey] %s:%s > %s' % (field[1], field[2], field[4])
  595.                     if not outputtext in reg_output:  # Ignore multiple CreateKeys. Only log the first.
  596.                         timelinetext = '%s,Registry,RegCreateKey,%s,%s,%s' % (field[0].split()[0].split('.')[0],
  597.                                                                               field[1], field[2], field[4])
  598.                         reg_output.append(outputtext)
  599.                         timeline.append(timelinetext)
  600.  
  601.             elif field[3] == 'RegSetValue' and field[5] == 'SUCCESS':
  602.                 if not blacklist_scan(reg_blacklist, field):
  603.                     reg_length = field[6].split('Length:')[1].split(',')[0].strip(whitespace + '"')
  604.                     if int(reg_length):
  605.                         data_field = field[6].split('Data:')[1].strip(whitespace + '"')
  606.                         if len(data_field.split(' ')) == 16:
  607.                             data_field += ' ...'
  608.                         outputtext = '[RegSetValue] %s:%s > %s  =  %s' % (field[1], field[2], field[4], data_field)
  609.                         timelinetext = '%s,Registry,RegSetValue,%s,%s,%s,%s' % (field[0].split()[0].split('.')[0],
  610.                                                                                 field[1], field[2], field[4],
  611.                                                                                 data_field)
  612.                         reg_output.append(outputtext)
  613.                         timeline.append(timelinetext)
  614.  
  615.             elif field[3] == 'RegDeleteValue':  # and field[5] == 'SUCCESS':
  616.                 # SUCCESS is commented out to allows all attempted deletions, whether or not the value exists
  617.                 if not blacklist_scan(reg_blacklist, field):
  618.                     outputtext = '[RegDeleteValue] %s:%s > %s' % (field[1], field[2], field[4])
  619.                     timelinetext = '%s,Registry,RegDeleteValue,%s,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  620.                                                                             field[2], field[4])
  621.                     reg_output.append(outputtext)
  622.                     timeline.append(timelinetext)
  623.  
  624.             elif field[3] == 'RegDeleteKey':  # and field[5] == 'SUCCESS':
  625.                 # SUCCESS is commented out to allows all attempted deletions, whether or not the value exists
  626.                 if not blacklist_scan(reg_blacklist, field):
  627.                     outputtext = '[RegDeleteKey] %s:%s > %s' % (field[1], field[2], field[4])
  628.                     timelinetext = '%s,Registry,RegDeleteKey,%s,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  629.                                                                           field[2], field[4])
  630.                     reg_output.append(outputtext)
  631.                     timeline.append(timelinetext)
  632.  
  633.             elif field[3] == 'UDP Send' and field[5] == 'SUCCESS':
  634.                 if not blacklist_scan(net_blacklist, field):
  635.                     server = field[4].split('-> ')[1]
  636.                     # TODO: work on this later, once I can verify it better.
  637.                     #if field[6] == 'Length: 20':
  638.                     #    output_line = '[DNS Query] %s:%s > %s' % (field[1], field[2], protocol_replace(server))
  639.                     #else:
  640.                     outputtext = '[UDP] %s:%s > %s' % (field[1], field[2], protocol_replace(server))
  641.                     if not outputtext in net_output:
  642.                         timelinetext = '%s,Network,UDP Send,%s,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  643.                                                                          field[2], protocol_replace(server))
  644.                         net_output.append(outputtext)
  645.                         timeline.append(timelinetext)
  646.  
  647.             elif field[3] == 'UDP Receive' and field[5] == 'SUCCESS':
  648.                 if not blacklist_scan(net_blacklist, field):
  649.                     server = field[4].split('-> ')[1]
  650.                     outputtext = '[UDP] %s > %s:%s' % (protocol_replace(server), field[1], field[2])
  651.                     if not outputtext in net_output:
  652.                         timelinetext = '%s,Network,UDP Receive,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  653.                                                                          field[2])
  654.                         net_output.append(outputtext)
  655.                         timeline.append(timelinetext)
  656.  
  657.             elif field[3] == 'TCP Send' and field[5] == 'SUCCESS':
  658.                 if not blacklist_scan(net_blacklist, field):
  659.                     server = field[4].split('-> ')[1]
  660.                     outputtext = '[TCP] %s:%s > %s' % (field[1], field[2], protocol_replace(server))
  661.                     if not outputtext in net_output:
  662.                         timelinetext = '%s,Network,TCP Send,%s,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  663.                                                                          field[2], protocol_replace(server))
  664.                         net_output.append(outputtext)
  665.                         timeline.append(timelinetext)
  666.  
  667.             elif field[3] == 'TCP Receive' and field[5] == 'SUCCESS':
  668.                 if not blacklist_scan(net_blacklist, field):
  669.                     server = field[4].split('-> ')[1]
  670.                     outputtext = '[TCP] %s > %s:%s' % (protocol_replace(server), field[1], field[2])
  671.                     if not outputtext in net_output:
  672.                         timelinetext = '%s,Network,TCP Receive,%s,%s' % (field[0].split()[0].split('.')[0], field[1],
  673.                                                                          field[2])
  674.                         net_output.append(outputtext)
  675.                         timeline.append(timelinetext)
  676.  
  677.         except IndexError:
  678.             if debug:
  679.                 sys.stderr.write(line)
  680.                 sys.stderr.write(format_exc())
  681.             error_output.append(original_line.strip())
  682.  
  683.         # Enumerate unique remote hosts into their own section
  684.         if server:
  685.             server = server.split(':')[0]
  686.             if not server in remote_servers and server != 'localhost':
  687.                 remote_servers.append(server)
  688.     #} End of file input processing
  689.  
  690.     report.append('Processes Created:')
  691.     report.append('==================')
  692.     for event in process_output:
  693.         report.append(event)
  694.  
  695.     report.append('')
  696.     report.append('File Activity:')
  697.     report.append('==================')
  698.     for event in file_output:
  699.         report.append(event)
  700.  
  701.     report.append('')
  702.     report.append('Registry Activity:')
  703.     report.append('==================')
  704.     for event in reg_output:
  705.         report.append(event)
  706.  
  707.     report.append('')
  708.     report.append('Network Traffic:')
  709.     report.append('==================')
  710.     for event in net_output:
  711.         report.append(event)
  712.  
  713.     report.append('')
  714.     report.append('Unique Hosts:')
  715.     report.append('==================')
  716.     for server in sorted(remote_servers):
  717.         report.append(protocol_replace(server).strip())
  718.  
  719.     if error_output:
  720.         report.append('\r\n\r\n\r\n\r\n\r\n\r\nERRORS DETECTED')
  721.         report.append('The following items could not be parsed correctly:')
  722.         for error in error_output:
  723.             report.append(error)
  724. # End of parse_csv()
  725.  
  726.  
  727. def main():
  728.     """
  729.    Main routine, parses arguments and calls other routines
  730.  
  731.    Arguments:
  732.        None
  733.    Results:
  734.        None
  735.    """
  736.     global generalize_paths
  737.     global timeout_seconds
  738.     global yara_folder
  739.     global use_pmc
  740.     global debug
  741.  
  742.     print('--===[ Noriben v%s ]===--' % __VERSION__)
  743.     print('--===[   @bbaskin   ]===--\r\n')
  744.  
  745.     parser = ArgumentParser()
  746.     parser.add_argument('-c', '--csv', help='Re-analyze an existing Noriben CSV file [input file]', required=False)
  747.     parser.add_argument('-p', '--pml', help='Re-analyze an existing Noriben PML file [input file]', required=False)
  748.     parser.add_argument('-f', '--filter', help='Specify alternate Procmon Filter PMC [input file]', required=False)
  749.     parser.add_argument('-t', '--timeout', help='Number of seconds to collect activity', required=False, type=int)
  750.     parser.add_argument('--output', help='Folder to store output files', required=False)
  751.     parser.add_argument('--yara', help='Folder containing YARA rules', required=False)
  752.     parser.add_argument('--generalize', dest='generalize_paths', default=False, action='store_true',
  753.                         help='Generalize file paths to their environment variables. Default: %s' % generalize_paths,
  754.                         required=False)
  755.     parser.add_argument('--cmd', help='Command line to execute (in quotes)', required=False)
  756.     parser.add_argument('-d', dest='debug', action='store_true', help='Enable debug tracebacks', required=False)
  757.     args = parser.parse_args()
  758.     report = list()
  759.     timeline = list()
  760.  
  761.     if args.debug:
  762.         debug = True
  763.  
  764.     # Check to see if string generalization is wanted
  765.     if args.generalize_paths:
  766.         generalize_paths = True
  767.  
  768.     if generalize_paths:
  769.         generalize_vars_init()
  770.  
  771.     # Check for a valid filter file
  772.     if args.filter:
  773.         if file_exists(args.filter):
  774.             pmc_file = args.filter
  775.         else:
  776.             pmc_file = 'ProcmonConfiguration.PMC'
  777.     else:
  778.         pmc_file = 'ProcmonConfiguration.PMC'
  779.  
  780.     if not file_exists(pmc_file):
  781.         use_pmc = False
  782.         print('[!] Filter file %s not found. Continuing without filters.' % pmc_file)
  783.     else:
  784.         use_pmc = True
  785.         print('[*] Using filter file: %s' % pmc_file)
  786.  
  787.     # Find a valid procmon executable.
  788.     procmonexe = check_procmon()
  789.     if not procmonexe:
  790.         print('[!] Unable to find Procmon (%s) in path.' % procmon)
  791.         sys.exit(1)
  792.  
  793.     # Check to see if specified output folder exists. If not, make it.
  794.     # This only works one path deep. In future, may make it recursive.
  795.     if args.output:
  796.         output_dir = args.output
  797.         if not os.path.exists(output_dir):
  798.             try:
  799.                 os.mkdir(output_dir)
  800.             except WindowsError:
  801.                 print('[!] Unable to create directory: %s' % output_dir)
  802.                 sys.exit(1)
  803.     else:
  804.         output_dir = ''
  805.  
  806.     # Check to see if specified YARA folder exists
  807.     if args.yara:
  808.         yara_folder = args.yara
  809.         if not yara_folder[-1] == '\\':
  810.             yara_folder += '\\'
  811.         if not os.path.exists(yara_folder):
  812.             print('[!] YARA rule path not found: %s' % yara_folder)
  813.             yara_folder = ''
  814.  
  815.     # Check if user-specified to rescan a PML
  816.     if args.pml:
  817.         if file_exists(args.pml):
  818.             # Reparse an existing PML
  819.             csv_file = output_dir + os.path.splitext(args.pml)[0] + '.csv'
  820.             txt_file = output_dir + os.path.splitext(args.pml)[0] + '.txt'
  821.             timeline_file = output_dir + os.path.splitext(args.pml)[0] + '_timeline.csv'
  822.  
  823.             process_PML_to_CSV(procmonexe, args.pml, pmc_file, csv_file)
  824.             if not file_exists(csv_file):
  825.                 print('[!] Error detected. Could not create CSV file: %s' % csv_file)
  826.                 sys.exit(1)
  827.  
  828.             parse_csv(csv_file, report, timeline)
  829.  
  830.             print('[*] Saving report to: %s' % txt_file)
  831.             codecs.open(txt_file, 'w', 'utf-8').write('\r\n'.join(report))
  832.  
  833.             print('[*] Saving timeline to: %s' % timeline_file)
  834.             codecs.open(timeline_file, 'w', 'utf-8').write('\r\n'.join(timeline))
  835.  
  836.             open_file_with_assoc(txt_file)
  837.             sys.exit()
  838.         else:
  839.             print('[!] PML file does not exist: %s\n' % args.pml)
  840.             parser.print_usage()
  841.             sys.exit(1)
  842.  
  843.     # Check if user-specified to rescan a CSV
  844.     if args.csv:
  845.         if file_exists(args.csv):
  846.             # Reparse an existing CSV
  847.             txt_file = os.path.splitext(args.csv)[0] + '.txt'
  848.             timeline_file = os.path.splitext(args.csv)[0] + '_timeline.csv'
  849.             parse_csv(args.csv, report, timeline)
  850.  
  851.             print('[*] Saving report to: %s' % txt_file)
  852.             codecs.open(txt_file, 'w', 'utf-8').write('\r\n'.join(report))
  853.  
  854.             print('[*] Saving timeline to: %s' % timeline_file)
  855.             codecs.open(timeline_file, 'w', 'utf-8').write('\r\n'.join(timeline))
  856.  
  857.             open_file_with_assoc(txt_file)
  858.             sys.exit()
  859.         else:
  860.             parser.print_usage()
  861.             sys.exit(1)
  862.  
  863.     if args.timeout:
  864.         timeout_seconds = args.timeout
  865.  
  866.     if args.cmd:
  867.         exe_cmdline = args.cmd
  868.     else:
  869.         exe_cmdline = ''
  870.  
  871.     # Start main data collection and processing
  872.     print('[*] Using procmon EXE: %s' % procmonexe)
  873.     session_id = get_session_name()
  874.     pml_file = output_dir + 'Noriben_%s.pml' % session_id
  875.     csv_file = output_dir + 'Noriben_%s.csv' % session_id
  876.     txt_file = output_dir + 'Noriben_%s.txt' % session_id
  877.     timeline_file = output_dir + 'Noriben_%s_timeline.csv' % session_id
  878.     print('[*] Procmon session saved to: %s' % pml_file)
  879.  
  880.     print('[*] Launching Procmon ...')
  881.     launch_procmon_capture(procmonexe, pml_file, pmc_file)
  882.  
  883.     if exe_cmdline:
  884.         print('[*] Launching command line: %s' % exe_cmdline)
  885.         subprocess.Popen(exe_cmdline)
  886.     else:
  887.         print('[*] Procmon is running. Run your executable now.')
  888.  
  889.     if timeout_seconds:
  890.         print('[*] Running for %d seconds. Press Ctrl-C to stop logging early.' % timeout_seconds)
  891.         # Print a small progress indicator, for those REALLY long sleeps.
  892.         try:
  893.             for i in range(timeout_seconds):
  894.                 progress = (100 / timeout_seconds) * i
  895.                 sys.stdout.write('\r%d%% complete' % progress)
  896.                 sys.stdout.flush()
  897.                 sleep(1)
  898.         except KeyboardInterrupt:
  899.             pass
  900.  
  901.     else:
  902.         print('[*] When runtime is complete, press CTRL+C to stop logging.')
  903.         try:
  904.             while True:
  905.                 sleep(10)
  906.         except KeyboardInterrupt:
  907.             pass
  908.  
  909.     print('\n[*] Termination of Procmon commencing... please wait')
  910.     terminate_procmon(procmonexe)
  911.  
  912.     print('[*] Procmon terminated')
  913.     if not file_exists(pml_file):
  914.         print('[!] Error creating PML file!')
  915.         sys.exit(1)
  916.  
  917.     # PML created, now convert it to a CSV for parsing
  918.     process_PML_to_CSV(procmonexe, pml_file, pmc_file, csv_file)
  919.     if not file_exists(csv_file):
  920.         print('[!] Error detected. Could not create CSV file: %s' % csv_file)
  921.         sys.exit(1)
  922.  
  923.     # Process CSV file, results in 'report' and 'timeline' output lists
  924.     parse_csv(csv_file, report, timeline)
  925.     print('[*] Saving report to: %s' % txt_file)
  926.     codecs.open(txt_file, 'w', 'utf-8').write('\r\n'.join(report))
  927.  
  928.     print('[*] Saving timeline to: %s' % timeline_file)
  929.     codecs.open(timeline_file, 'w', 'utf-8').write('\r\n'.join(timeline))
  930.  
  931.     open_file_with_assoc(txt_file)
  932.     # End of main()
  933.  
  934. if __name__ == '__main__':
  935.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement