# Copyright (C) 2010-2012 Cuckoo Sandbox Developers. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org # See the file 'docs/LICENSE' for copying permission. import os import sys import random import shutil import logging import xmlrpclib import ConfigParser from ctypes import * from threading import Lock, Thread, Timer from lib.api.process import Process from lib.common.exceptions import CuckooError from lib.common.abstracts import Package from lib.common.defines import * from lib.common.paths import PATHS from lib.core.config import Config from lib.core.startup import create_folders, init_logging from lib.core.privileges import grant_debug_privilege from lib.core.packages import choose_package from lib.core.screenshots import Screenshots log = logging.getLogger() BUFSIZE = 512 FILES_LIST = [] PROCESS_LIST = [] PROCESS_LOCK = Lock() def add_file(file_path): """Add a file to file list.""" if file_path.startswith("\\\\.\\"): return if file_path.startswith("\\??\\"): file_path = file_path[4:] if os.path.exists(file_path): if file_path not in FILES_LIST: log.info("Added new file to list with path: %s" % file_path) FILES_LIST.append(file_path) def add_pid(pid): """Add a process to process list.""" PROCESS_LOCK.acquire() if type(pid) == long or type(pid) == int or type(pid) == str: log.info("Added new process to list with pid: %d" % pid) PROCESS_LIST.append(pid) PROCESS_LOCK.release() def add_pids(pids): """Add PID.""" if type(pids) == list: for pid in pids: add_pid(pid) else: add_pid(pids) def dump_files(): """Dump dropped file.""" for file_path in FILES_LIST: file_name = os.path.basename(file_path) while True: dir_path = os.path.join(PATHS["files"], str(random.randint(100000000, 9999999999))) if os.path.exists(dir_path): continue try: os.mkdir(dir_path) dump_path = os.path.join(dir_path, "%s.bin" % file_name) except OSError as e: dump_path = os.path.join(PATHS["files"], "%s.bin" % file_name) break try: shutil.copy(file_path, dump_path) log.info("Dropped file \"%s\" dumped successfully to path \"%s\"" % (file_path, dump_path)) except (IOError, shutil.Error) as e: log.error("Unable to dump dropped file at path \"%s\": %s" % (file_path, e)) class PipeHandler(Thread): """PIPE handler, reads on PIPE.""" def __init__(self, h_pipe): """@param h_pipe: PIPE to read.""" Thread.__init__(self) self.h_pipe = h_pipe def run(self): """Run handler. @return: operation status. """ data = "" while True: bytes_read = c_int(0) buf = create_string_buffer(BUFSIZE) success = KERNEL32.ReadFile(self.h_pipe, buf, sizeof(buf), byref(bytes_read), None) data += buf.value if not success and KERNEL32.GetLastError() == ERROR_MORE_DATA: continue #elif not success or bytes_read.value == 0: # if KERNEL32.GetLastError() == ERROR_BROKEN_PIPE: # pass break if data: command = data.strip() #log.debug("Connection received (data=%s)" % command) if command.startswith("PID:"): pid = command[4:] if pid.isdigit(): pid = int(pid) if pid not in PROCESS_LIST: add_pids(pid) proc = Process(pid=pid) proc.inject() KERNEL32.WriteFile(self.h_pipe, create_string_buffer("OK"), 2, byref(bytes_read), None) elif command.startswith("FILE:"): file_path = command[5:] add_file(file_path) return True class PipeServer(Thread): """Cuckoo PIPE server.""" def __init__(self, pipe_name = "\\\\.\\pipe\\cuckoo"): """@param pipe_name: Cuckoo PIPE server name.""" Thread.__init__(self) self.pipe_name = pipe_name self.do_run = True def stop(self): """Stop PIPE server.""" self.do_run = False def run(self): """Create and run PIPE server. @return: operation status. """ while self.do_run: h_pipe = KERNEL32.CreateNamedPipeA(self.pipe_name, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | \ PIPE_READMODE_MESSAGE | \ PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, None) if h_pipe == INVALID_HANDLE_VALUE: return False if KERNEL32.ConnectNamedPipe(h_pipe, None): handler = PipeHandler(h_pipe) handler.daemon = True handler.start() else: KERNEL32.CloseHandle(h_pipe) return True class Analyzer: """Cuckoo analyzer. Runs in guest and perform sample analysis.""" def __init__(self): self.do_run = True self.pipe = None self.config = None self.file_path = None def prepare(self): """Prepare env for analysis.""" grant_debug_privilege() create_folders() init_logging() self.config = Config(cfg=os.path.join(PATHS["root"], "analysis.conf")) self.pipe = PipeServer() self.pipe.daemon = True self.pipe.start() #self.file_path = os.path.join(os.environ["SYSTEMDRIVE"] + os.sep, self.config.file_name) self.file_path = os.path.join("C:\\Users\\avtest\\Documents" + os.sep, self.config.file_name) def get_options(self): """Get analysis options. @return: options dict. """ options = {} if self.config.options: try: fields = self.config.options.strip().split(",") for field in fields: try: key, value = field.strip().split("=") except ValueError as e: log.warning("Failed parsing option (%s): %s" % (field, e.message)) continue options[key.strip()] = value.strip() except ValueError: pass return options def complete(self): """End analysis.""" self.pipe.stop() dump_files() log.info("Analysis completed") def stop(self): """Stop analysis process.""" self.do_run = False def run(self): """Run analysis. @return: operation status. """ self.prepare() if not self.config.package: log.info("No analysis package specified, trying to detect it automagically") package = choose_package(self.config.file_type) if not package: raise CuckooError("No valid package available for file type: %s" % self.config.file_type) else: log.info("Automatically selected analysis package \"%s\"" % package) else: package = self.config.package package_name = "packages.%s" % package try: __import__(package_name, globals(), locals(), ["dummy"], -1) except ImportError: raise CuckooError("Unable to import package \"%s\", does not exist." % package_name) Package() try: package_class = Package.__subclasses__()[0] except IndexError as e: raise CuckooError("Unable to select package class (package=%s): %s" % (package_name, e.message)) pack = package_class(self.get_options()) timer = Timer(self.config.timeout, self.stop) timer.start() shots = Screenshots() shots.start() try: pids = pack.start(self.file_path) except NotImplementedError: raise CuckooError("The package \"%s\" doesn't contain a run function." % package_name) add_pids(pids) while self.do_run: PROCESS_LOCK.acquire() try: for pid in PROCESS_LIST: if not Process(pid=pid).is_alive(): log.info("Process with pid %d has terminated" % pid) PROCESS_LIST.remove(pid) if len(PROCESS_LIST) == 0: timer.cancel() break try: if not pack.check(): timer.cancel() break except NotImplementedError: pass finally: PROCESS_LOCK.release() KERNEL32.Sleep(1000) try: pack.finish() except NotImplementedError: pass from time import sleep sleep(10) shots.stop() self.complete() return True if __name__ == "__main__": success = False error = "" try: analyzer = Analyzer() success = analyzer.run() except KeyboardInterrupt: error = "Keyboard Interrupt" except CuckooError as e: error = e.message if len(log.handlers) > 0: log.critical(error) else: sys.stderr.write("%s\n" % e.message) finally: server = xmlrpclib.Server("http://127.0.0.1:8000") if error: server.complete(success, error) else: server.complete(success) .