Advertisement
opexxx

unpack.py

Jul 12th, 2014
333
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.03 KB | None | 0 0
  1. ##############################################################
  2. # Python script to attempt automatic unpacking/decrypting of #
  3. # malware samples using WinAppDbg.                           #
  4. #                                                            #
  5. # unpack.py v2013.02.26                                      #
  6. # http://malwaremusings.com/scripts/unpack.py                #
  7. ##############################################################
  8.  
  9. import sys
  10. import traceback
  11. import winappdbg
  12.  
  13. # Log file which we log info to
  14. logfile = None
  15.  
  16. class MyEventHandler(winappdbg.EventHandler):
  17.  
  18. ###
  19. # A. Declaring variables
  20. ###
  21.  
  22.   # A.1 used to keep track of allocated executable memory
  23.   allocedmem = {}
  24.  
  25.   # A.2 used to indicate that we've found the entry point
  26.   entrypt = 0x00000000
  27.  
  28.   #
  29.   # variables used to find and disassemble unpacking loop
  30.   #
  31.  
  32.   # A.3 used to indicate that we're single stepping
  33.   tracing = False
  34.  
  35.   # A.4 remember the last two eip values
  36.   lasteip = [0x00000000,0x00000000]
  37.  
  38.   # A.5 lowest eip address we see
  39.   lowesteip = 0xffffffff
  40.  
  41.   # A.6 highest eip address we see
  42.   highesteip = 0x00000000
  43.  
  44.   # A.7 list of addresses which we've disassembled
  45.   disasmd = []
  46.  
  47.   # A.8 keeps track of addresses and instructions
  48.   #     that write to the allocated memory block(s)
  49.   writeaddrs = {}
  50.  
  51. ###
  52. # B. Class methods (functions)
  53. ###
  54.  
  55.   ### B.1
  56.   # get_funcargs(event)
  57.   #     query winappdbg to get the function arguments
  58.   #
  59.   #     return a tuple consisting of the return address
  60.   #     and a sub-tuple of function arguments
  61.   ###
  62.  
  63.   def get_funcargs(self,event):
  64.     h = event.hook
  65.     t = event.get_thread()
  66.     tid = event.get_tid()
  67.  
  68.     return (t.get_pc(),h.get_params(tid))
  69.  
  70. ###
  71. # C. API Hooks
  72. ###
  73.  
  74.   ### C.1
  75.   # apiHooks: winappdbg defined hash of API calls to hook
  76.   #
  77.   #     Each entry is indexed by library name and is an array of
  78.   #     tuples consisting of API call name and number of args
  79.   ###
  80.  
  81.   apiHooks = {
  82.     "kernel32.dll":[
  83.       ("VirtualAllocEx",5),
  84.     ]
  85.   }
  86.  
  87.   ###
  88.   # API hook callback functions
  89.   #
  90.   #     These are defined by winappdbg and consist of functions
  91.   #     named pre_<apifuncname> and post_<apifuncname> which are
  92.   #     called on entry to, and on exit from, the given API
  93.   #     function (<apifuncname>), respectively.
  94.   ###
  95.  
  96.   # C.2
  97.   # VirtualAllocEx() hook(s)
  98.   #
  99.  
  100.   def post_VirtualAllocEx(self,event,retval):
  101.     try:
  102.       # C.2.1 Get the return address and arguments
  103.  
  104.       (ra,(hProcess,lpAddress,dwSize,flAllocationType,flProtect)) = self.get_funcargs(event)
  105.  
  106.       # Get an instance to the debugger which triggered the event
  107.       # and also the process id and thread id of the process to which
  108.       # the event pertains
  109.  
  110.       d = event.debug
  111.       pid = event.get_pid()
  112.       tid = event.get_tid()
  113.  
  114.       # Log the fact that we've seen a VirtualAllocEx() call
  115.  
  116.       log("[*] <%d:%d> 0x%x: VirtualAllocEx(0x%x,0x%x,%d,0x%x,0x%03x) = 0x%x" % (pid,tid,ra,hProcess,lpAddress,dwSize,flAllocationType,flProtect,retval))
  117.  
  118.       # C.2.2 All the memory protection bits which include EXECUTE
  119.       # permission use bits 4 - 7, which is nicely matched
  120.       # by masking (ANDing) it with 0xf0 and checking for a
  121.       # non-zero result
  122.  
  123.       if (flProtect & 0x0f0):
  124.         log("[*]     Request for EXECUTEable memory")
  125.  
  126.         # We can only set page guards on our own process
  127.         # otherwise page guard exception will occur in
  128.         # system code when this process attempts to write
  129.         # to the allocated memory.
  130.         # This causes ZwWriteVirtualMemory() to fail
  131.  
  132.         # We can, however, set a page guard on it when
  133.         # this process creates the remote thread, as it
  134.         # will have presumably stopped writing to the
  135.         # other process' memory at that point.
  136.  
  137.         # C.2.2.1 Check that this VirtualAllocEx() call is for
  138.         # the current process (hProcess == -1), and if
  139.         # so, ask the winappdbg debugger instance to
  140.         # create a page guard on the memory region.
  141.         # Also add information about the allocated region
  142.         # to our allocedmem hash, indexed by pid and
  143.         # base address.
  144.  
  145.         if (hProcess == 0xffffffff):
  146.           d.watch_buffer(pid,retval,dwSize - 1)
  147.           self.allocedmem[(pid,retval)] = dwSize
  148.     except:
  149.       traceback.print_exc()
  150.       raise
  151.  
  152. ###
  153. # D. winappdbg debug event handlers
  154. ###
  155.  
  156.   ### D.1
  157.   # create_process
  158.   #
  159.   #     winappdbg defined callback function to handle process creation events
  160.   ###
  161.  
  162.   def create_process(self,event):
  163.     try:
  164.       proc = event.get_process()
  165.  
  166.       log("[*] Create process event for pid %d (%s)" % (proc.get_pid(),proc.get_image_name()))
  167.     except:
  168.       traceback.print_exc()
  169.       raise
  170.  
  171.   ### D.2
  172.   # exit_process
  173.   #
  174.   #     winappdbg defined callback function to handle process exit events
  175.   ###
  176.  
  177.   def exit_process(self,event):
  178.     log("[*] Exit process event for pid %d (%s): %d" % (event.get_pid(),event.get_filename(),event.get_exit_code()))
  179.  
  180.   ### D.3
  181.   # create_thread
  182.   #
  183.   #     winappdbg defined callback function to handle thread creation events
  184.   ###
  185.  
  186.   def create_thread(self,event):
  187.     log("[*] Create thread event")
  188.  
  189.   ### D.4
  190.   # load_dll
  191.   #
  192.   #     winappdbg defined callback function to handle DLL load events
  193.   ###
  194.  
  195.   def load_dll(self,event):
  196.     log("[*] Load DLL")
  197.  
  198.   ### D.5
  199.   # event
  200.   #
  201.   #     winappdbg defined callback function to handle any remaining events
  202.   ###
  203.  
  204.   def event(self,event):
  205.     log("[*] Unhandled event: %s" % event.get_event_name())
  206.  
  207. ###
  208. # E. winappdbg debug exception handlers
  209. ###
  210.  
  211.   ### E.1
  212.   # guard_page
  213.   #
  214.   #     winappdbg defined callback function to handle guard page exceptions
  215.   ###
  216.  
  217.   def guard_page(self,exception):
  218.     try:
  219.       # E.1.1 Get the exception and fault information that we need
  220.       f_type = exception.get_fault_type()
  221.  
  222.       e_addr = exception.get_exception_address()
  223.       f_addr = exception.get_fault_address()
  224.  
  225.       # get the process and thread ids
  226.       pid = exception.get_pid()
  227.       tid = exception.get_tid()
  228.  
  229.       # It is interesting to log this, but it generates a lot of log
  230.       # output and slows the whole process down
  231.       #log("[!] <%d:%d> 0x%x: GUARD_PAGE(%d) exception for address 0x%x" % (pid,tid,e_addr,f_type,f_addr))
  232.       #log("[*] VirtualAlloc()d memory address 0x%x accessed (%d) from 0x%x (%s)" % (f_addr,f_type,e_addr,instr))
  233.  
  234.       # E.1.2 Was it a memory write operation?
  235.       if (f_type == winappdbg.win32.EXCEPTION_WRITE_FAULT):
  236.         # E.1.2.1 Use the writeaddrs[] array to check to see
  237.         #         if we have already logged access from this
  238.         #         address, as unpacking is generally done in
  239.         #         a loop and we don't want to log the same
  240.         #         instructions for each iteration
  241.         if not e_addr in self.writeaddrs:
  242.           t = exception.get_thread()
  243.           instr = t.disassemble_instruction(e_addr)[2].lower()
  244.           log("[*] VirtualAlloc()d memory address 0x%x written from 0x%x (%s)" % (f_addr,e_addr,instr))
  245.           self.writeaddrs[e_addr] = instr
  246.  
  247.         # E.1.2.2 Use the tracing variable to see if we have
  248.         #         already started tracing, that is single
  249.         #         stepping. If not, enable it, and make a note
  250.         #         of the fact by setting the tracing variable
  251.         #         to True
  252.         if not self.tracing:
  253.           self.tracing = True
  254.           d = exception.debug
  255.           d.start_tracing(exception.get_tid())
  256.  
  257.       # E.1.3 Was it a memory instruction fetch (execute) operation,
  258.       #       and if so, are we still looking for the entry point address?
  259.       if (f_type == winappdbg.win32.EXCEPTION_EXECUTE_FAULT) and (self.entrypt == 0):
  260.         self.entrypt = e_addr
  261.         t = exception.get_thread()
  262.         jmpinstr = t.disassemble_instruction(self.lasteip[0])[2].lower()
  263.  
  264.         # E.1.3.1 Log what we've found
  265.         log("[D]     lasteip[1]: 0x%x" % self.lasteip[1])
  266.         log("[*]     Found unpacked entry point at 0x%x called from 0x%x (%s)" % (self.entrypt,self.lasteip[0],jmpinstr))
  267.         log("[-]     Unpacking loop at 0x%x - 0x%x" % (self.lowesteip,self.highesteip))
  268.  
  269.         pid = exception.get_pid()
  270.  
  271.         # E.1.3.2
  272.         for (mem_pid,memblk) in self.allocedmem:
  273.           if (mem_pid == pid):
  274.             size = self.allocedmem[(mem_pid,memblk)]
  275.             endaddr = memblk + size - 1
  276.             if (e_addr >= memblk) and (e_addr <= endaddr):
  277.               # E.1.3.3 Log what we're doing and delete the memory breakpoint
  278.               log("[-]     Dumping %d bytes of memory range 0x%x - 0x%x" % (size,memblk,endaddr))
  279.               d = exception.debug
  280.               d.dont_watch_buffer(exception.get_pid(),memblk,size - 1)
  281.  
  282.               # E.1.3.4 Disable single-step debugging
  283.               self.tracing = False
  284.               d.stop_tracing(exception.get_tid())
  285.  
  286.               # E.1.3.5 Reset unpacking loop variables
  287.               self.entrypt = 0x00000000
  288.               #del self.lasteip
  289.               self.lasteip = [0x00000000,0x00000000]
  290.               self.lowesteip = 0xffffffff
  291.               self.highest = 0x00000000
  292.  
  293.               # E.1.3.6 Dump the memory block to a file
  294.               p = exception.get_process()
  295.  
  296.               dumpfile = open(sys.argv[1] + ".memblk0x%08x" % memblk,"wb")
  297.               dumpfile.write(p.read(memblk,size))
  298.               dumpfile.close()
  299.     except Exception as e:
  300.       traceback.print_exc()
  301.       raise
  302.  
  303.   ### E.2
  304.   # single_step
  305.   #
  306.   #     winappdbg defined callback function to handle single step exceptions
  307.   ###
  308.  
  309.   def single_step(self,exception):
  310.     try:
  311.       # E.2.1 Get the exception address
  312.       e_addr = exception.get_exception_address()
  313.  
  314.       # E.2.2 If we have just looped back (eip has gone backward)
  315.       if (e_addr < self.lasteip[1]):
  316.         # Remember this lower address as the lowest loop address
  317.         if self.lowesteip == 0xffffffff: self.lowesteip = e_addr
  318.  
  319.         # ... and the address we just jumped from as the highest loop address
  320.         if self.highesteip == 0x00000000: self.highesteip = self.lasteip[1]
  321.  
  322.       # E.2.3 If we are executing an instruction within the bounds of the loop
  323.       #       and we haven't already disassembled this address, then do so
  324.       if (e_addr >= self.lowesteip) and (e_addr <= self.highesteip) and (not e_addr in self.disasmd):
  325.         t = exception.get_thread()
  326.         disasm = t.disassemble_instruction(e_addr)
  327.         instr = disasm[2].lower()
  328.         log("    0x%x: %s" % (e_addr,instr))
  329.         self.disasmd.append(e_addr)
  330.  
  331.       # E.2.4 Remember the last two instruction addresses (eip values)
  332.       #       We need to remember the last two in order to be able to
  333.       #       disassemble the instruction that jumped to the original
  334.       #       entry point in the unpacked code
  335.       self.lasteip[0] = self.lasteip[1]
  336.       self.lasteip[1] = e_addr
  337.     except Exception as e:
  338.       traceback.print_exc()
  339.       raise
  340.  
  341.   ### E.3
  342.   # exception
  343.   #
  344.   #     winappdbg defined callback function to handle remaining exceptions
  345.   ###
  346.  
  347.   def exception(self,exception):
  348.     log("[*] Unhandled exception at 0x%x: %s" % (exception.get_exception_address(),exception.get_exception_name()))
  349.  
  350. #
  351. #### end of MyEventHandler class
  352. #
  353.  
  354. ###
  355. # F. Miscellaneous functions
  356. ###
  357.  
  358. ### F.1
  359. # log(msg):
  360. ###
  361. def log(msg):
  362.   global logfile
  363.  
  364.   print(msg)
  365.   if not logfile:
  366.     logfile = open(sys.argv[1] + ".log","w")
  367.   if logfile:
  368.     logfile.write(msg + "\n")
  369.     logfile.flush()
  370.  
  371. ### F.2
  372. # simple_debugger(argv):
  373. ###
  374. def simple_debugger(filename):
  375.   global logfile
  376.  
  377.   try:
  378.     handler = MyEventHandler()
  379.     #logfile = winappdbg.textio.Logger(filename + ".log",verbose = True)
  380.   except:
  381.     traceback.print_exc()
  382.   with winappdbg.Debug(handler,bKillOnExit = True) as debug:
  383.     log("[*] Starting %s" % filename)
  384.     debug.execl(filename)
  385.     log("[*] Starting debug loop")
  386.     debug.loop()
  387.     log("[*] Terminating")
  388.  
  389. ###
  390. # G. Start of script execution
  391. ###
  392.  
  393. simple_debugger(sys.argv[1])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement