modules.processing.procmemory

Package modules :: Package processing :: Module procmemory |
[hide private]
[frames] | no frames]

# Source Code for Module modules.processing.procmemory

      1  # Copyright (C) 2010-2013 Claudio Guarnieri.
      2  # Copyright (C) 2014-2016 Cuckoo Foundation.
      3  # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
      4  # See the file 'docs/LICENSE' for copying permission.
      5
      6  import hashlib
      7  import logging
      8  import os
      9  import re
     10  import struct
     11
     12  from lib.cuckoo.common.abstracts import Processing
     13  from lib.cuckoo.common.objects import File
     14
     15  try:
     16      import pefile
     17      HAVE_PEFILE = True
     18  except ImportError:
     19      HAVE_PEFILE = False
     20
     21  log = logging.getLogger(__name__)
     22
     23  PAGE_READONLY = 0x00000002
     24  PAGE_READWRITE = 0x00000004
     25  PAGE_WRITECOPY = 0x00000008
     26  PAGE_EXECUTE = 0x00000010
     27  PAGE_EXECUTE_READ = 0x00000020
     28  PAGE_EXECUTE_READWRITE = 0x00000040
     29  PAGE_EXECUTE_WRITECOPY = 0x00000080
     30
     31  page_access = {
     32      PAGE_READONLY: "r",
     33      PAGE_READWRITE: "rw",
     34      PAGE_WRITECOPY: "rwc",
     35      PAGE_EXECUTE: "rx",
     36      PAGE_EXECUTE_READ: "rx",
     37      PAGE_EXECUTE_READWRITE: "rwx",
     38      PAGE_EXECUTE_WRITECOPY: "rwxc",
     39  }
     40
     41 -class ProcessMemory(Processing):
     42      """Analyze process memory dumps."""
     43 -    def read_dump(self, filepath):
     44          f = open(filepath, "rb")
     45
     46          while True:
     47              buf = f.read(24)
     48              if not buf:
     49                  break
     50
     51              row = struct.unpack("QIIII", buf)
     52              addr, size, state, typ, protect = row
     53
     54              yield {
     55                  "addr": "0x%08x" % addr,
     56                  "end": "0x%08x" % (addr + size),
     57                  "size": size,
     58                  "type": typ,
     59                  "protect": page_access.get(protect),
     60                  "offset": f.tell(),
     61              }
     62
     63              f.seek(size, 1)
     64
     65 -    def create_idapy(self, process):
     66          i = open(process["file"], "rb")
     67          o = open(process["file"].replace(".dmp", ".py"), "wb")
     68
     69          print>>o, "from idaapi import add_segm, mem2base, autoMark, AU_CODE"
     70          print>>o, "from idaapi import set_processor_type, SETPROC_ALL"
     71          print>>o, "set_processor_type('80386r', SETPROC_ALL)"
     72
     73          for idx, region in enumerate(process["regions"]):
     74              i.seek(region["offset"])
     75
     76              if not region["protect"]:
     77                  section = "unk_%d" % idx
     78                  type_ = "DATA"
     79              elif "x" in region["protect"]:
     80                  section = "text_%d" % idx
     81                  type_ = "CODE"
     82              elif "w" in region["protect"]:
     83                  section = "data_%d" % idx
     84                  type_ = "DATA"
     85              else:
     86                  section = "rdata_%d" % idx
     87                  type_ = "DATA"
     88
     89              print>>o, "add_segm(0, %s, %s, '%s', '%s')" % (
     90                  region["addr"], region["end"], section, type_
     91              )
     92              print>>o, "mem2base('%s'.decode('base64'), %s)" % (
     93                  i.read(region["size"]).encode("base64").replace("\n", ""),
     94                  region["addr"]
     95              )
     96              if type_ == "CODE":
     97                  print>>o, "autoMark(%s, AU_CODE)" % region["addr"]
     98
     99 -    def _fixup_pe_header(self, pe):
    100          """Fixes the PE header from an in-memory representation to an
    101          on-disk representation."""
    102          for section in pe.sections:
    103              section.PointerToRawData = section.VirtualAddress
    104              section.SizeOfRawData = max(
    105                  section.Misc_VirtualSize, section.SizeOfRawData
    106              )
    107
    108          reloc = pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_BASERELOC"]
    109          if len(pe.OPTIONAL_HEADER.DATA_DIRECTORY) < reloc:
    110              return
    111
    112          reloc = pe.OPTIONAL_HEADER.DATA_DIRECTORY[reloc]
    113          if not reloc.VirtualAddress or not reloc.Size:
    114              return
    115
    116          # Disable relocations as those have already been applied.
    117          reloc.VirtualAddress = reloc.Size = 0
    118          pe.FILE_HEADER.Characteristics |= \
    119              pefile.IMAGE_CHARACTERISTICS["IMAGE_FILE_RELOCS_STRIPPED"]
    120
    121 -    def dump_images(self, process, drop_dlls=False):
    122          """Dump executable images from this process memory dump."""
    123          buf = open(process["file"], "rb").read()
    124
    125          images, capture, regions, end, pe = [], False, [], None, None
    126          for r in process["regions"]:
    127              off, size = r["offset"], r["size"]
    128
    129              if capture:
    130                  if int(r["end"], 16) > end:
    131                      images.append((pe, regions))
    132                      capture = False
    133                  else:
    134                      regions.append(r)
    135                  continue
    136
    137              # We're going to take a couple of assumptions for granted here.
    138              # Namely, the PE header is fully intact, has not been tampered
    139              # with, and the DOS header, the NT header, and the Optional header
    140              # all remain in the first page/chunk of this PE file.
    141              if buf[off:off+2] != "MZ":
    142                  continue
    143
    144              try:
    145                  pe = pefile.PE(data=buf[off:off+size], fast_load=True)
    146              except pefile.PEFormatError:
    147                  continue
    148
    149              # Enable the capture of memory regions.
    150              capture, regions = True, [r]
    151              end = int(r["addr"], 16) + pe.OPTIONAL_HEADER.SizeOfImage
    152
    153          # If present, also process the last loaded executable.
    154          if capture and regions:
    155              images.append((pe, regions))
    156
    157          for pe, regions in images:
    158              img = []
    159
    160              # Skip DLLs if requested to do so (the default).
    161              if pe.is_dll() and not drop_dlls:
    162                  continue
    163
    164              self._fixup_pe_header(pe)
    165
    166              img.append(str(pe.write()))
    167              for r in regions:
    168                  img.append(buf[r["offset"]:r["offset"]+r["size"]])
    169
    170              sha1 = hashlib.sha1("".join(img)).hexdigest()
    171
    172              if pe.is_dll():
    173                  filename = "%s-%s.dll_" % (process["pid"], sha1[:16])
    174              elif pe.is_exe():
    175                  filename = "%s-%s.exe_" % (process["pid"], sha1[:16])
    176              else:
    177                  log.warning(
    178                      "Unknown injected executable for pid=%s", process["pid"]
    179                  )
    180                  continue
    181
    182              filepath = os.path.join(self.pmemory_path, filename)
    183              open(filepath, "wb").write("".join(img))
    184
    185              yield File(filepath).get_all()
    186
    187 -    def run(self):
    188          """Run analysis.
    189          @return: structured results.
    190          """
    191          self.key = "procmemory"
    192          results = []
    193
    194          if self.options.get("extract_img") and not HAVE_PEFILE:
    195              log.warning(
    196                  "In order to extract PE files from memory dumps it is "
    197                  "required to have pefile installed (`pip install pefile`)."
    198              )
    199
    200          if os.path.exists(self.pmemory_path):
    201              for dmp in os.listdir(self.pmemory_path):
    202                  if not dmp.endswith(".dmp"):
    203                      continue
    204
    205                  dump_path = os.path.join(self.pmemory_path, dmp)
    206                  dump_file = File(dump_path)
    207
    208                  pid, num = map(int, re.findall("(\\d+)", dmp))
    209
    210                  proc = dict(
    211                      file=dump_path, pid=pid, num=num,
    212                      yara=dump_file.get_yara("memory"),
    213                      urls=list(dump_file.get_urls()),
    214                      regions=list(self.read_dump(dump_path)),
    215                  )
    216
    217                  if self.options.get("idapro"):
    218                      self.create_idapy(proc)
    219
    220                  if self.options.get("extract_img") and HAVE_PEFILE:
    221                      proc["extracted"] = list(self.dump_images(proc))
    222
    223                  if self.options.get("dump_delete"):
    224                      try:
    225                          os.remove(dump_path)
    226                      except OSError:
    227                          log.error("Unable to delete memory dump file at path \"%s\"", dump_path)
    228
    229                  results.append(proc)
    230
    231          results.sort(key=lambda x: (x["pid"], x["num"]))
    232          return results
    233