Advertisement
opexxx

tracer.py

Jul 12th, 2014
465
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.61 KB | None | 0 0
  1. #
  2. # Simple tracer
  3. #
  4. # Assists along with IDA Pro in determining the code
  5. # exercised by a specific functionality
  6. #
  7. # Note: It uses WinAppDbg to "stalk" or "break at" all process' functions
  8. # The callback takes care of the logging process
  9.  
  10.  
  11. from winappdbg import Debug, EventHandler, HexDump, Process, CrashDump
  12. import struct
  13. import sys
  14. import optparse
  15.  
  16. AUTHOR = "Carlos Garcia Prado <carlos.g.prado@gmail.com>"
  17.  
  18.  
  19. # Global vars... Ugly
  20. fd = None
  21. searchPattern = ""
  22. interestingFunctions = list()
  23. logged_functions = list()
  24.  
  25.  
  26. ############################################################################################
  27. def main():
  28.  
  29.     global fd, searchPattern     # To modify it
  30.     arg_check = False
  31.     PROG_INFO = (
  32.              "File automatically generated by %s.\n"
  33.              "%s\n\n"
  34.              % (sys.argv[0], AUTHOR)
  35.              )
  36.    
  37.     ############################################################################
  38.     # Parsing arguments always SUCKS
  39.     parser = optparse.OptionParser()
  40.    
  41.     parser.add_option('-n', '--noise', help = 'Records noise data', action = 'store_true', dest = 'noise')
  42.     parser.add_option('-s', '--signal', help = 'Records signal data', action = 'store_true', dest = 'signal')
  43.     parser.add_option('-c', '--compare', help = 'Compares signal to noise', action = 'store_true', dest = 'compare')
  44.     parser.add_option('-a', '--argument', help = 'Search for arguments', action = 'store_true', dest = 'argument')
  45.     parser.add_option('-w', '--search_pattern', help = 'Pattern to search for', action = 'store', type = 'string', dest = 'search_pattern')
  46.     parser.add_option('-p', '--program', help = 'Program to record', action = 'store', type = 'string', dest = 'prog')
  47.     parser.add_option('-f', '--funcfile', help = 'File containing all function addresses', action = 'store', type = 'string', dest = 'funcfile')
  48.            
  49.     (opts, args) = parser.parse_args()
  50.        
  51.  
  52.     # Mandatory Option (except with '-c')
  53.     if opts.prog is None and opts.compare is None:
  54.         print "[Error] What program did you say?\n"
  55.         parser.print_help()
  56.         sys.exit(1)
  57.     else:
  58.         program_file = opts.prog
  59.        
  60.     # Where is the functions file?
  61.     if opts.funcfile is None:
  62.         if opts.compare is None:
  63.             print "[Error] I need a file with all function addresses.\n"
  64.             parser.print_help()
  65.             sys.exit(1)
  66.     else:
  67.         address_file = opts.funcfile
  68.    
  69.     # Word(s) to look for?
  70.     if opts.argument:
  71.         if opts.search_pattern is None:
  72.             print "[Error] I need word(s) to look for. Check the 'w' option.\n"
  73.             parser.print_help()
  74.             sys.exit(1)
  75.    
  76.     # What do you want to do?    
  77.     if opts.noise:
  78.         output_filename = 'noise.txt'
  79.     elif opts.signal:
  80.         output_filename = 'signal.txt'
  81.     elif opts.compare:
  82.         compare()
  83.         generateFuncRangesFile()
  84.         sys.exit(0)
  85.     elif opts.argument:
  86.         output_filename = 'argument_check.txt'
  87.         searchPattern = opts.search_pattern
  88.         arg_check = True
  89.     else:
  90.         print "[Error] You must specify an option :)\n"
  91.         parser.print_help()
  92.         sys.exit(1)
  93.  
  94.  
  95.  
  96.     fd = open(output_filename, 'w')
  97.    
  98.     '''
  99.    Write some short header.
  100.    No more open files and thinking "what was this?"
  101.    '''
  102.  
  103.     fd.write("Analysis of program: %s\n" % program_file)
  104.     if opts.argument:
  105.         fd.write("Looking for pattern: %s\n" % opts.search_pattern)
  106.     fd.write(PROG_INFO)
  107.    
  108.    
  109.     simple_debugger(address_file, program_file, arg_check)
  110.        
  111.     print "[info] Trace finalized."
  112.  
  113.  
  114. ############################################################################################
  115. def check_args_callback(event):
  116.     '''
  117.    This will be called when our breakpoint is hit. Checks if our string is a parameter.
  118.    @param event: Event information, dear Watson.
  119.    @todo: dereference the values in registers as well {eax, ebx, ecx, esi, edi}
  120.    '''        
  121.     nrOfArguments = 5  # TODO: Take this parameter from IDA
  122.    
  123.     MAX_USERSPACE_ADDRESS = 0x7FFFFFFF
  124.     MIN_USERSPACE_ADDRESS = 0x1000
  125.     MAX_ARGUMENT_LEN = 100  # somehow arbitrary
  126.    
  127.     process = event.get_process()
  128.     thread  = event.get_thread()
  129.     Eip     = thread.get_pc()
  130.     Esp     = thread.get_context()['Esp']
  131.     stackAddress = Esp + 4
  132.    
  133.     for idx in xrange(nrOfArguments):
  134.         stackAddress += idx * 4
  135.         # Dereference at address and look for searchPattern
  136.         # NOTE: read() returns a string, not a number (unpack does the trick)
  137.         suspectedPointer = struct.unpack('<L', process.read(stackAddress, 4))[0]
  138.        
  139.         if suspectedPointer > MIN_USERSPACE_ADDRESS and suspectedPointer < MAX_USERSPACE_ADDRESS:
  140.             try:
  141.                 possibleString = process.read(suspectedPointer, MAX_ARGUMENT_LEN) # This is already a string, cool
  142.                 if searchPattern in possibleString:
  143.                     if Eip not in logged_functions:
  144.                         logged_functions.append(Eip)
  145.                         print "[*] Found! %s is the parameter nr. %d of %08x" % (searchPattern, idx + 1, Eip)
  146.                         fd.write("[*] Found! %s is the %d parameter of %08x\n" % (searchPattern, idx + 1, Eip))
  147.                         fd.write("%s\n" % HexDump.hexblock(possibleString, suspectedPointer))
  148.             except KeyboardInterrupt:
  149.                 fd.close()
  150.                 sys.exit(1)
  151.             except:
  152.                 # Access violation. Log only by debugging (huge overhead due to I/O)
  153.                 pass
  154.            
  155.             # Let's search for the string in UNICODE
  156.             possibleStringU = process.peek_string(suspectedPointer, fUnicode = True)
  157.             if searchPattern in possibleStringU:
  158.                 if searchPattern in possibleString:
  159.                     if Eip not in logged_functions:
  160.                         logged_functions.append(Eip)
  161.                         print "[*] Found! %s is the parameter nr. %d of %08x" % (searchPattern, idx + 1, Eip)
  162.                         fd.write("[*] Found! %s is the %d parameter of %08x\n" % (searchPattern, idx + 1, Eip))
  163.                         fd.write("%s\n" % HexDump.hexblock(possibleString, suspectedPointer))
  164.    
  165.    
  166.  
  167.  
  168. ############################################################################################
  169. def log_eip_callback(event):
  170.     '''
  171.    This will be called when our breakpoint is hit. It writes the current EIP.
  172.    @param event: Event information, dough!
  173.    '''        
  174.    
  175.     address = event.get_thread().get_pc()
  176.     fd.write(HexDump.address(address) + '\n')
  177.    
  178.    
  179. ############################################################################################
  180. class HitTracerEventHandler(EventHandler):
  181.     '''
  182.    The moment we attach to the running process the create_process method will be called.
  183.    In this case it will set breakpoints at every function.
  184.    @param address_file: The function containing all the addresses (from IDA)
  185.    @param program_file: The executable's name
  186.    '''
  187.    
  188.     def __init__(self, address_file, program_file, arg_check = False):
  189.         self.address_file   = address_file
  190.         self.program_file   = program_file
  191.         self.arg_check      = arg_check
  192.        
  193.        
  194.     def create_process(self, event):   # misleading name, also called when attaching :)
  195.        
  196.         # I need the process PID
  197.         module = event.get_module()
  198.        
  199.         if module.match_name(self.program_file):
  200.             pid = event.get_pid()
  201.            
  202.             # Read the file containing the function EAs
  203.             f = open(self.address_file, "r")
  204.             functionAddresses = f.readlines()
  205.             f.close()
  206.            
  207.             nr_of_breakpoints = 0
  208.            
  209.             print "[*] Preparing breakpoints. Please wait..."
  210.            
  211.             for f_str in functionAddresses:
  212.                 func_start_address = int(f_str.strip().split('-')[0], 16)
  213.                
  214.                 if self.arg_check:
  215.                     # Sets a permanent breakpoint (hit every time)
  216.                     event.debug.break_at(pid, func_start_address, check_args_callback)
  217.                 else:
  218.                     # Sets a one-shot breakpoint (removed after first hit)
  219.                     event.debug.stalk_at(pid, func_start_address, log_eip_callback)
  220.                    
  221.                 nr_of_breakpoints += 1
  222.            
  223.            
  224.             print "[Debug] Installed %d breakpoints" % nr_of_breakpoints
  225.  
  226.  
  227.  
  228. ############################################################################################
  229. def compare():
  230.     '''
  231.    Compares both files and filters the signal.
  232.    It outputs to "specific_functions.txt"
  233.    '''
  234.    
  235.     global interestingFunctions
  236.     OUTPUTFILENAME = 'specific_functions.txt'
  237.    
  238.     f = open('noise.txt', 'r')
  239.     noise = f.readlines()
  240.     f.close()
  241.    
  242.     g = open('signal.txt', 'r')
  243.     signal = g.readlines()
  244.     g.close()
  245.    
  246.     nr_of_functions = 0
  247.     o_file = open(OUTPUTFILENAME, 'w')
  248.    
  249.     for func_str in signal:
  250.         if func_str not in noise:
  251.             '''
  252.            NOTE: the noise, signal and specific function files have leading zeros, since
  253.            they are the output of HexDump.address(). The IDA Pro generated file hasn't.
  254.            Since I want to compare this afterwards, I will lstrip() the leading zeros now.
  255.            Moreover, the IDA Pro file outputs the hex in lowercase format :)
  256.            '''
  257.             interestingFunctions.append(func_str.strip().lstrip('0').lower())
  258.             o_file.write(func_str)
  259.             nr_of_functions += 1
  260.    
  261.     o_file.close()
  262.    
  263.     print "[*] Dumped %d unique functions to %s" % (nr_of_functions, OUTPUTFILENAME)
  264.  
  265.  
  266. ############################################################################################
  267. def generateFuncRangesFile():
  268.     '''
  269.    It generates an additional file containing not only the
  270.    function start addresses but the ending as well.
  271.    This is suitable to feed a Basic Block Level tracer in order to
  272.    specify interesting code to trace.
  273.    '''
  274.    
  275.     INPUTFILENAME   = 'function_addresses.txt'
  276.     OUTPUTFILENAME  = 'specific_functions_intervals.txt'
  277.    
  278.     print "[*] Generating the function intervals file. Please wait..."
  279.    
  280.     try:
  281.         # I need the original file (generated by IDA Pro)
  282.         fd_ida = open(INPUTFILENAME, 'r')
  283.         idaFuncInfo = fd_ida.readlines()
  284.         fd_ida.close()
  285.     except:
  286.         print "[debug] Fatal. Couldn't open file: %s" % INPUTFILENAME
  287.         sys.exit(1)
  288.    
  289.  
  290.  
  291.     out_file = open(OUTPUTFILENAME, 'w')
  292.    
  293.     for interval in idaFuncInfo:
  294.         if interval.split('-')[0] in interestingFunctions:
  295.             out_file.write(interval)
  296.  
  297.     out_file.close()
  298.    
  299.    
  300.     print "[*] Interesting functions intervals file generated: %s" % OUTPUTFILENAME
  301.  
  302.    
  303. ############################################################################################    
  304. def simple_debugger(address_file, program_file, arg_check):
  305.    
  306.     process = None
  307.     debug = Debug(HitTracerEventHandler(address_file, program_file, arg_check))
  308.    
  309.    
  310.     try:
  311.         # Lookup currently running processes
  312.         debug.system.scan_processes()
  313.        
  314.         for (process, name) in debug.system.find_processes_by_filename(program_file):
  315.             print "[*] Found %d: %s" % (process.get_pid(), name)
  316.            
  317.             # Attach to it
  318.             debug.attach(process.get_pid())
  319.            
  320.         if process == None:
  321.             print "[*] Fatal. Process not found. Is it running?"
  322.             sys.exit(1)
  323.            
  324.         # Wait for all debugees to finish
  325.         debug.loop()
  326.        
  327.     # Cleanup actions
  328.     finally:
  329.         debug.stop()
  330.        
  331.  
  332.  
  333. ############################################################################################
  334. def print_logo():
  335.     ''' It prints an old school ascii logo :) '''
  336.  
  337.     LOGO = (
  338.             " ______   ______     ______     ______     ______     ______    \n"
  339.             "/\__  _\ /\ == \  /\ __ \  /\ ___\  /\ ___\  /\ == \  \n"
  340.             "\/_/\ \/ \ \ __<   \ \ __ \ \ \ \____  \ \ __\  \ \ __<   \n"
  341.             "   \ \_\ \ \_\ \_\ \ \_\ \_\ \ \_____\ \ \_____\ \ \_\ \_\ \n"
  342.             "    \/_/   \/_/ /_/   \/_/\/_/   \/_____/   \/_____/   \/_/ /_/ \n"
  343.             )
  344.    
  345.     print "%s\n" % LOGO
  346.     print "%s\n\n" % AUTHOR
  347.  
  348.  
  349. ############################################################################################
  350.  
  351. if __name__ == "__main__":
  352.     print_logo()
  353.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement