def __init__(self): self.config = Config(cfg="analysis.conf") self.enabled = self.config.procmon
def get_task_id(self): conf = Config("c:\\cuckoo\\analysis.conf") return conf.task_id
def inject(self, dll=None, apc=False): """Cuckoo DLL injection. @param dll: Cuckoo DLL path. @param apc: APC use. """ if not self.pid: log.warning("No valid pid specified, injection aborted") return False if not self.is_alive(): log.warning( "The process with pid %s is not alive, " "injection aborted", self.pid) return False if not dll: dll = "cuckoomon.dll" dll = randomize_dll(os.path.join("dll", dll)) if not dll or not os.path.exists(dll): log.warning( "No valid DLL specified to be injected in process " "with pid %d, injection aborted.", self.pid) return False arg = KERNEL32.VirtualAllocEx(self.h_process, None, len(dll) + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE) if not arg: log.error( "VirtualAllocEx failed when injecting process with " "pid %d, injection aborted (Error: %s)", self.pid, get_error_string(KERNEL32.GetLastError())) return False bytes_written = c_int(0) if not KERNEL32.WriteProcessMemory(self.h_process, arg, dll + "\x00", len(dll) + 1, byref(bytes_written)): log.error( "WriteProcessMemory failed when injecting process with " "pid %d, injection aborted (Error: %s)", self.pid, get_error_string(KERNEL32.GetLastError())) return False kernel32_handle = KERNEL32.GetModuleHandleA("kernel32.dll") load_library = KERNEL32.GetProcAddress(kernel32_handle, "LoadLibraryA") config_path = os.path.join(os.getenv("TEMP"), "%s.ini" % self.pid) with open(config_path, "w") as config: cfg = Config("analysis.conf") cfgoptions = cfg.get_options() # The first time we come up with a random startup-time. if Process.first_process: # This adds 1 up to 30 times of 20 minutes to the startup # time of the process, therefore bypassing anti-vm checks # which check whether the VM has only been up for <10 minutes. Process.startup_time = random.randint(1, 30) * 20 * 60 * 1000 config.write("host-ip={0}\n".format(cfg.ip)) config.write("host-port={0}\n".format(cfg.port)) config.write("pipe={0}\n".format(PIPE)) config.write("results={0}\n".format(PATHS["root"])) config.write("analyzer={0}\n".format(os.getcwd())) config.write("first-process={0}\n".format( "1" if Process.first_process else "0")) config.write("startup-time={0}\n".format(Process.startup_time)) config.write("shutdown-mutex={0}\n".format(SHUTDOWN_MUTEX)) config.write("force-sleepskip={0}\n".format( cfgoptions.get("force-sleepskip", "0"))) Process.first_process = False event_name = "CuckooEvent%d" % self.pid self.event_handle = KERNEL32.CreateEventA(None, False, False, event_name) if not self.event_handle: log.warning("Unable to create notify event..") return False if apc or self.suspended: log.debug("Using QueueUserAPC injection.") if not self.h_thread: log.info( "No valid thread handle specified for injecting " "process with pid %d, injection aborted.", self.pid) self.event_handle = None return False if not KERNEL32.QueueUserAPC(load_library, self.h_thread, arg): log.error( "QueueUserAPC failed when injecting process with " "pid %d (Error: %s)", self.pid, get_error_string(KERNEL32.GetLastError())) self.event_handle = None return False else: log.debug("Using CreateRemoteThread injection.") new_thread_id = c_ulong(0) thread_handle = KERNEL32.CreateRemoteThread( self.h_process, None, 0, load_library, arg, 0, byref(new_thread_id)) if not thread_handle: log.error( "CreateRemoteThread failed when injecting process " "with pid %d (Error: %s)", self.pid, get_error_string(KERNEL32.GetLastError())) KERNEL32.CloseHandle(self.event_handle) self.event_handle = None return False else: KERNEL32.CloseHandle(thread_handle) log.info("Successfully injected process with pid %d." % self.pid) return True
def inject(self, dll=None, interest=None, nosleepskip=False): """Cuckoo DLL injection. @param dll: Cuckoo DLL path. @param interest: path to file of interest, handed to cuckoomon config @param apc: APC use. """ global LOGSERVER_POOL if not self.pid: return False thread_id = 0 if self.thread_id: thread_id = self.thread_id if not self.is_alive(): log.warning( "The process with pid %s is not alive, " "injection aborted", self.pid) return False is_64bit = self.is_64bit() if not dll: if is_64bit: dll = CUCKOOMON64_NAME else: dll = CUCKOOMON32_NAME else: os.path.join("dll", dll) dll = os.path.join(os.getcwd(), dll) if not dll or not os.path.exists(dll): log.warning( "No valid DLL specified to be injected in process " "with pid %d, injection aborted.", self.pid) return False if thread_id or self.suspended: log.debug("Using QueueUserAPC injection.") else: log.debug("Using CreateRemoteThread injection.") config_path = "C:\\%s.ini" % self.pid with open(config_path, "w") as config: cfg = Config("analysis.conf") cfgoptions = cfg.get_options() # start the logserver for this monitored process logserver_path = LOGSERVER_PREFIX + str(self.pid) if logserver_path not in LOGSERVER_POOL: LOGSERVER_POOL[logserver_path] = LogServer( cfg.ip, cfg.port, logserver_path) firstproc = Process.first_process config.write("host-ip={0}\n".format(cfg.ip)) config.write("host-port={0}\n".format(cfg.port)) config.write("pipe={0}\n".format(PIPE)) config.write("logserver={0}\n".format(logserver_path)) config.write("results={0}\n".format(PATHS["root"])) config.write("analyzer={0}\n".format(os.getcwd())) config.write( "first-process={0}\n".format("1" if firstproc else "0")) config.write("startup-time={0}\n".format(Process.startup_time)) config.write("file-of-interest={0}\n".format(interest)) config.write("shutdown-mutex={0}\n".format(SHUTDOWN_MUTEX)) config.write("terminate-event={0}{1}\n".format( TERMINATE_EVENT, self.pid)) if nosleepskip: config.write("force-sleepskip=0\n") if "norefer" not in cfgoptions: config.write("referrer={0}\n".format( get_referrer_url(interest))) simple_optnames = [ "force-sleepskip", "full-logs", "force-flush", "no-stealth", "buffer-max", "large-buffer-max", "serial", "sysvol_ctimelow", "sysvol_ctimehigh", "sys32_ctimelow", "sys32_ctimehigh", "debug", "disable_hook_content", "hook-type", "exclude-apis", "exclude-dlls", ] for optname in simple_optnames: if optname in cfgoptions: config.write("{0}={1}\n".format(optname, cfgoptions[optname])) if firstproc: Process.first_process = False orig_bin_name = "" bit_str = "" if is_64bit: orig_bin_name = LOADER64_NAME bit_str = "64-bit" else: orig_bin_name = LOADER32_NAME bit_str = "32-bit" bin_name = os.path.join(os.getcwd(), orig_bin_name) if os.path.exists(bin_name): ret = subprocess.call( [bin_name, "inject", str(self.pid), str(thread_id), dll]) if ret != 0: if ret == 1: log.info("Injected into suspended %s process with pid %d", bit_str, self.pid) else: log.error( "Unable to inject into %s process with pid %d, error: %d", bit_str, self.pid, ret) return False else: return True else: log.error( "Please place the %s binary from cuckoomon into analyzer/windows/bin in order to analyze %s binaries.", os.path.basename(bin_name), bit_str) return False
def run(self): """Run handler. @return: operation status. """ global MONITORED_SERVICES global LASTINJECT_TIME try: data = "" response = "OK" # Read the data submitted to the Pipe Server. 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() # Debug, Regular, Warning, or Critical information from CuckooMon. if command.startswith("DEBUG:"): log.debug(command[6:]) elif command.startswith("INFO:"): log.info(command[5:]) elif command.startswith("WARNING:"): log.warning(command[8:]) elif command.startswith("CRITICAL:"): log.critical(command[9:]) # Parse the prefix for the received notification. # In case of GETPIDS we're gonna return the current process ID # and the process ID of our parent process (agent.py). elif command == "GETPIDS": hidepids = set() hidepids.update(HIDE_PIDS) hidepids.update([PID, PPID]) response = struct.pack("%dI" % len(hidepids), *hidepids) # When analyzing we don't want to hook all functions, as we're # having some stability issues with regards to webbrowsers. elif command == "HOOKDLLS": is_url = Config(cfg="analysis.conf").category != "file" url_dlls = "ntdll", "kernel32" def hookdll_encode(names): # We have to encode each dll name as unicode string # with length 16. names = [ name + "\x00" * (16 - len(name)) for name in names ] f = lambda s: "".join(ch + "\x00" for ch in s) return "".join(f(name) for name in names) # If this sample is not a URL, then we don't want to limit # any API hooks (at least for now), so we write a null-byte # which indicates that all DLLs should be hooked. if not is_url: response = "\x00" else: response = hookdll_encode(url_dlls) # remove pid from process list because we received a notification # from kernel land elif command.startswith("KTERMINATE:"): data = command[11:] process_id = int(data) if process_id: if process_id in PROCESS_LIST: remove_pid(process_id) # same than below but we don't want to inject any DLLs because # it's a kernel analysis elif command.startswith("KPROCESS:"): PROCESS_LOCK.acquire() data = command[9:] process_id = int(data) thread_id = None if process_id: if process_id not in (PID, PPID): if process_id not in PROCESS_LIST: proc = Process(pid=process_id, thread_id=thread_id) filepath = proc.get_filepath() filename = os.path.basename(filepath) if not in_protected_path(filename): add_pid(process_id) log.info("Announce process name : %s", filename) PROCESS_LOCK.release() elif command.startswith("KERROR:"): error_msg = command[7:] log.error("Error : %s", str(error_msg)) # if a new driver has been loaded, we stop the analysis elif command == "KSUBVERT": for pid in PROCESS_LIST: log.info("Process with pid %s has terminated", pid) PROCESS_LIST.remove(pid) # Handle case of a service being started by a monitored process # Switch the service type to own process behind its back so we # can monitor the service more easily with less noise elif command.startswith("SERVICE:"): servname = command[8:] si = subprocess.STARTUPINFO() si.dwFlags = subprocess.STARTF_USESHOWWINDOW si.wShowWindow = subprocess.SW_HIDE subprocess.call("sc config " + servname + " type= own", startupinfo=si) log.info("Announced starting service \"%s\"", servname) if not MONITORED_SERVICES: # Inject into services.exe so we can monitor service creation # if tasklist previously failed to get the services.exe PID we'll be # unable to inject if SERVICES_PID: servproc = Process(pid=SERVICES_PID, suspended=False) filepath = servproc.get_filepath() servproc.inject(dll=DEFAULT_DLL, interest=filepath, nosleepskip=True) LASTINJECT_TIME = datetime.now() servproc.close() KERNEL32.Sleep(1000) MONITORED_SERVICES = True else: log.error('Unable to monitor service %s' % (servname)) # For now all we care about is bumping up our LASTINJECT_TIME to account for long delays between # injection and actual resume time where the DLL would have a chance to load in the new process # and report back to have its pid added to the list of monitored processes elif command.startswith("RESUME:"): LASTINJECT_TIME = datetime.now() # Handle case of malware terminating a process -- notify the target # ahead of time so that it can flush its log buffer elif command.startswith("KILL:"): PROCESS_LOCK.acquire() process_id = int(command[5:]) if process_id not in (PID, PPID) and process_id in PROCESS_LIST: # only notify processes we've hooked event_name = TERMINATE_EVENT + str(process_id) event_handle = KERNEL32.OpenEventA( EVENT_MODIFY_STATE, False, event_name) if not event_handle: log.warning( "Unable to open termination event for pid %u.", process_id) else: log.info( "Notified of termination of process with pid %u.", process_id) # dump the memory of exiting processes if self.options.get("procmemdump"): p = Process(pid=process_id) p.dump_memory() # make sure process is aware of the termination KERNEL32.SetEvent(event_handle) KERNEL32.CloseHandle(event_handle) PROCESS_LOCK.release() # Handle notification of cuckoomon loading in a process elif command.startswith("LOADED:"): PROCESS_LOCK.acquire() process_id = int(command[7:]) if process_id not in PROCESS_LIST: add_pids(process_id) PROCESS_LOCK.release() log.info( "Cuckoomon successfully loaded in process with pid %u.", process_id) # In case of PID, the client is trying to notify the creation of # a new process to be injected and monitored. elif command.startswith("PROCESS:"): # We acquire the process lock in order to prevent the analyzer # to terminate the analysis while we are operating on the new # process. PROCESS_LOCK.acquire() # Set the current DLL to the default one provided # at submission. dll = DEFAULT_DLL suspended = False # We parse the process ID. data = command[8:] if len(data) > 2 and data[1] == ':': if data[0] == '1': suspended = True data = command[10:] process_id = thread_id = None if "," not in data: if data.isdigit(): process_id = int(data) elif data.count(",") == 1: process_id, param = data.split(",") thread_id = None if process_id.isdigit(): process_id = int(process_id) else: process_id = None if param.isdigit(): thread_id = int(param) if process_id: if process_id not in (PID, PPID): # We inject the process only if it's not being # monitored already, otherwise we would generate # polluted logs. if process_id not in PROCESS_LIST: # Open the process and inject the DLL. # Hope it enjoys it. proc = Process(pid=process_id, thread_id=thread_id, suspended=suspended) filepath = proc.get_filepath() is_64bit = proc.is_64bit() filename = os.path.basename(filepath) log.info( "Announced %s process name: %s pid: %d", "64-bit" if is_64bit else "32-bit", filename, process_id) if not in_protected_path(filename): res = proc.inject(dll, filepath) LASTINJECT_TIME = datetime.now() proc.close() else: log.warning( "Received request to inject Cuckoo " "process with pid %d, skip", process_id) # Once we're done operating on the processes list, we release # the lock. PROCESS_LOCK.release() # In case of FILE_NEW, the client is trying to notify the creation # of a new file. elif command.startswith("FILE_NEW:"): # We extract the file path. file_path = unicode(command[9:].decode("utf-8")) # We add the file to the list. add_file(file_path) # In case of FILE_DEL, the client is trying to notify an ongoing # deletion of an existing file, therefore we need to dump it # straight away. elif command.startswith("FILE_DEL:"): # Extract the file path. file_path = unicode(command[9:].decode("utf-8")) # Dump the file straight away. del_file(file_path) elif command.startswith("FILE_MOVE:"): # Syntax = "FILE_MOVE:old_file_path::new_file_path". if "::" in command[10:]: old_fname, new_fname = command[10:].split("::", 1) move_file(unicode(old_fname.decode("utf-8")), unicode(new_fname.decode("utf-8"))) else: log.warning("Received unknown command from cuckoomon: %s", command) KERNEL32.WriteFile(self.h_pipe, create_string_buffer(response), len(response), byref(bytes_read), None) KERNEL32.CloseHandle(self.h_pipe) return True except Exception as e: error_exc = traceback.format_exc() log.exception(error_exc) return True
def prepare(self): """Prepare env for analysis.""" # Get SeDebugPrivilege for the Python process. It will be needed in # order to perform the injections. grant_privilege("SeDebugPrivilege") grant_privilege("SeLoadDriverPrivilege") # Initialize logging. init_logging() # Parse the analysis configuration file generated by the agent. self.config = Config(cfg="analysis.conf") # Pass the configuration through to the Process class. Process.set_config(self.config) # Set virtual machine clock. set_clock( datetime.datetime.strptime(self.config.clock, "%Y%m%dT%H:%M:%S")) # Set the default DLL to be used for this analysis. self.default_dll = self.config.options.get("dll") # If a pipe name has not set, then generate a random one. self.config.pipe = self.get_pipe_path( self.config.options.get("pipe", random_string(16, 32))) # Generate a random name for the logging pipe server. self.config.logpipe = self.get_pipe_path(random_string(16, 32)) # Initialize and start the Command Handler pipe server. This is going # to be used for communicating with the monitored processes. self.command_pipe = PipeServer(PipeDispatcher, self.config.pipe, message=True, dispatcher=CommandPipeHandler(self)) self.command_pipe.daemon = True self.command_pipe.start() # Initialize and start the Log Pipe Server - the log pipe server will # open up a pipe that monitored processes will use to send logs to # before they head off to the host machine. destination = self.config.ip, self.config.port self.log_pipe_server = PipeServer(PipeForwarder, self.config.logpipe, destination=destination) self.log_pipe_server.daemon = True self.log_pipe_server.start() # We update the target according to its category. If it's a file, then # we store the target path. if self.config.category == "file": self.target = os.path.join(os.environ["TEMP"], self.config.file_name) elif self.config.category == "archive": zip_path = os.path.join(os.environ["TEMP"], self.config.file_name) zipfile.ZipFile(zip_path).extractall(os.environ["TEMP"]) self.target = os.path.join(os.environ["TEMP"], self.config.options["filename"]) # If it's a URL, well.. we store the URL. else: self.target = self.config.target
def __init__(self): self.config = Config(cfg="analysis.conf") self.pids_reported = set()
def kernel_analyze(self): """zer0m0n kernel analysis """ log.info("Starting kernel analysis") log.info("Installing driver") if is_os_64bit(): sys_file = os.path.join(os.getcwd(), "dll", "zer0m0n_x64.sys") else: sys_file = os.path.join(os.getcwd(), "dll", "zer0m0n.sys") exe_file = os.path.join(os.getcwd(), "dll", "logs_dispatcher.exe") if not sys_file or not exe_file or not os.path.exists( sys_file) or not os.path.exists(exe_file): log.warning( "No valid zer0m0n files to be used for process with pid %d, injection aborted", self.pid) return False exe_name = random_string(6) service_name = random_string(6) driver_name = random_string(6) inf_data = '[Version]\r\nSignature = "$Windows NT$"\r\nClass = "ActivityMonitor"\r\nClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}\r\nProvider= %Prov%\r\nDriverVer = 22/01/2014,1.0.0.0\r\nCatalogFile = %DriverName%.cat\r\n[DestinationDirs]\r\nDefaultDestDir = 12\r\nMiniFilter.DriverFiles = 12\r\n[DefaultInstall]\r\nOptionDesc = %ServiceDescription%\r\nCopyFiles = MiniFilter.DriverFiles\r\n[DefaultInstall.Services]\r\nAddService = %ServiceName%,,MiniFilter.Service\r\n[DefaultUninstall]\r\nDelFiles = MiniFilter.DriverFiles\r\n[DefaultUninstall.Services]\r\nDelService = %ServiceName%,0x200\r\n[MiniFilter.Service]\r\nDisplayName= %ServiceName%\r\nDescription= %ServiceDescription%\r\nServiceBinary= %12%\\%DriverName%.sys\r\nDependencies = "FltMgr"\r\nServiceType = 2\r\nStartType = 3\r\nErrorControl = 1\r\nLoadOrderGroup = "FSFilter Activity Monitor"\r\nAddReg = MiniFilter.AddRegistry\r\n[MiniFilter.AddRegistry]\r\nHKR,,"DebugFlags",0x00010001 ,0x0\r\nHKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%\r\nHKR,"Instances\\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%\r\nHKR,"Instances\\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%\r\n[MiniFilter.DriverFiles]\r\n%DriverName%.sys\r\n[SourceDisksFiles]\r\n' + driver_name + '.sys = 1,,\r\n[SourceDisksNames]\r\n1 = %DiskId1%,,,\r\n[Strings]\r\n' + 'Prov = "' + random_string( 8 ) + '"\r\nServiceDescription = "' + random_string( 12 ) + '"\r\nServiceName = "' + service_name + '"\r\nDriverName = "' + driver_name + '"\r\nDiskId1 = "' + service_name + ' Device Installation Disk"\r\nDefaultInstance = "' + service_name + ' Instance"\r\nInstance1.Name = "' + service_name + ' Instance"\r\nInstance1.Altitude = "370050"\r\nInstance1.Flags = 0x0' new_inf = os.path.join(os.getcwd(), "dll", "{0}.inf".format(service_name)) new_sys = os.path.join(os.getcwd(), "dll", "{0}.sys".format(driver_name)) copy(sys_file, new_sys) new_exe = os.path.join(os.getcwd(), "dll", "{0}.exe".format(exe_name)) copy(exe_file, new_exe) log.info("[-] Driver name : " + new_sys) log.info("[-] Inf name : " + new_inf) log.info("[-] Application name : " + new_exe) log.info("[-] Service : " + service_name) fh = open(new_inf, "w") fh.write(inf_data) fh.close() os_is_64bit = is_os_64bit() if os_is_64bit: wow64 = c_ulong(0) KERNEL32.Wow64DisableWow64FsRedirection(byref(wow64)) os.system( 'cmd /c "rundll32 setupapi.dll, InstallHinfSection DefaultInstall 132 ' + new_inf + '"') os.system("net start " + service_name) si = STARTUPINFO() si.cb = sizeof(si) pi = PROCESS_INFORMATION() cr = CREATE_NEW_CONSOLE ldp = KERNEL32.CreateProcessW(new_exe, None, None, None, None, cr, None, os.getenv("TEMP"), byref(si), byref(pi)) if not ldp: if os_is_64bit: KERNEL32.Wow64RevertWow64FsRedirection(wow64) log.error("Failed starting " + exe_name + ".exe.") return False config_path = os.path.join(os.getenv("TEMP"), "%s.ini" % self.pid) with open(config_path, "w") as config: cfg = Config("analysis.conf") config.write("host-ip={0}\n".format(cfg.ip)) config.write("host-port={0}\n".format(cfg.port)) config.write("pipe={0}\n".format(PIPE)) log.info("Sending startup information") hFile = KERNEL32.CreateFileW(PATH_KERNEL_DRIVER, GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, 0, None) if os_is_64bit: KERNEL32.Wow64RevertWow64FsRedirection(wow64) if hFile: p = Process(pid=os.getpid()) ppid = p.get_parent_pid() pid_vboxservice = 0 pid_vboxtray = 0 # get pid of VBoxService.exe and VBoxTray.exe proc_info = PROCESSENTRY32() proc_info.dwSize = sizeof(PROCESSENTRY32) snapshot = KERNEL32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) flag = KERNEL32.Process32First(snapshot, byref(proc_info)) while flag: if proc_info.sz_exeFile == "VBoxService.exe": log.info("VBoxService.exe found !") pid_vboxservice = proc_info.th32ProcessID flag = 0 elif proc_info.sz_exeFile == "VBoxTray.exe": pid_vboxtray = proc_info.th32ProcessID log.info("VBoxTray.exe found !") flag = 0 flag = KERNEL32.Process32Next(snapshot, byref(proc_info)) bytes_returned = c_ulong(0) msg = str(self.pid) + "_" + str(ppid) + "_" + str( os.getpid()) + "_" + str(pi.dwProcessId) + "_" + str( pid_vboxservice) + "_" + str(pid_vboxtray) + '\0' KERNEL32.DeviceIoControl(hFile, IOCTL_PID, msg, len(msg), None, 0, byref(bytes_returned), None) msg = os.getcwd() + '\0' KERNEL32.DeviceIoControl(hFile, IOCTL_CUCKOO_PATH, str(msg, 'utf-8'), len(str(msg, 'utf-8')), None, 0, byref(bytes_returned), None) else: log.warning("Failed to access kernel driver") return True
def prepare(self): """Prepare env for analysis.""" # Get SeDebugPrivilege for the Python process. It will be needed in # order to perform the injections. grant_debug_privilege() # Initialize logging. init_logging() # Parse the analysis configuration file generated by the agent. self.config = Config(cfg="analysis.conf") # Pass the configuration through to the Process class. Process.set_config(self.config) # Set virtual machine clock. clock = datetime.strptime(self.config.clock, "%Y%m%dT%H:%M:%S") # Setting date and time. # NOTE: Windows system has only localized commands with date format # following localization settings, so these commands for english date # format cannot work in other localizations. # In addition DATE and TIME commands are blocking if an incorrect # syntax is provided, so an echo trick is used to bypass the input # request and not block analysis. os.system("echo:|date {0}".format(clock.strftime("%m-%d-%y"))) os.system("echo:|time {0}".format(clock.strftime("%H:%M:%S"))) # Set the default DLL to be used for this analysis. self.default_dll = self.config.options.get("dll") # If a pipe name has not set, then generate a random one. if "pipe" in self.config.options: self.config.pipe = "\\\\.\\PIPE\\%s" % self.config.options["pipe"] else: self.config.pipe = "\\\\.\\PIPE\\%s" % random_string(16, 32) # Generate a random name for the logging pipe server. self.config.logpipe = "\\\\.\\PIPE\\%s" % random_string(16, 32) # Initialize and start the Command Handler pipe server. This is going # to be used for communicating with the monitored processes. self.command_pipe = PipeServer(PipeDispatcher, self.config.pipe, message=True, dispatcher=CommandPipeHandler(self)) self.command_pipe.daemon = True self.command_pipe.start() # Initialize and start the Log Pipe Server - the log pipe server will # open up a pipe that monitored processes will use to send logs to # before they head off to the host machine. destination = self.config.ip, self.config.port self.log_pipe_server = PipeServer(PipeForwarder, self.config.logpipe, destination=destination) self.log_pipe_server.daemon = True self.log_pipe_server.start() # We update the target according to its category. If it's a file, then # we store the target path. if self.config.category == "file": self.target = os.path.join(os.environ["TEMP"] + os.sep, self.config.file_name) # If it's a URL, well.. we store the URL. else: self.target = self.config.target
def inject(self, dll=None, interest=None, nosleepskip=False): """Cuckoo DLL injection. @param dll: Cuckoo DLL path. @param interest: path to file of interest, handed to cuckoomon config @param apc: APC use. """ if not self.pid: log.warning("No valid pid specified, injection aborted") return False thread_id = 0 if self.thread_id: thread_id = self.thread_id if not self.is_alive(): log.warning( "The process with pid %s is not alive, " "injection aborted", self.pid) return False is_64bit = self.is_64bit() if not dll: if is_64bit: dll = "cuckoomon_x64.dll" else: dll = "cuckoomon.dll" dll = randomize_bin(os.path.join("dll", dll), "dll") if not dll or not os.path.exists(dll): log.warning( "No valid DLL specified to be injected in process " "with pid %d, injection aborted.", self.pid) return False config_path = "C:\\%s.ini" % self.pid with open(config_path, "w") as config: cfg = Config("analysis.conf") cfgoptions = cfg.get_options() # start the logserver for this monitored process self.logserver = LogServer(cfg.ip, cfg.port, self.logserver_path) firstproc = Process.first_process config.write("host-ip={0}\n".format(cfg.ip)) config.write("host-port={0}\n".format(cfg.port)) config.write("pipe={0}\n".format(PIPE)) config.write("logserver={0}\n".format(self.logserver_path)) config.write("results={0}\n".format(PATHS["root"])) config.write("analyzer={0}\n".format(os.getcwd())) config.write( "first-process={0}\n".format("1" if firstproc else "0")) config.write("startup-time={0}\n".format(Process.startup_time)) config.write("file-of-interest={0}\n".format(interest)) config.write("shutdown-mutex={0}\n".format(SHUTDOWN_MUTEX)) config.write("terminate-event={0}{1}\n".format( TERMINATE_EVENT, self.pid)) if nosleepskip: config.write("force-sleepskip=0\n") elif "force-sleepskip" in cfgoptions: config.write("force-sleepskip={0}\n".format( cfgoptions["force-sleepskip"])) if "full-logs" in cfgoptions: config.write("full-logs={0}\n".format(cfgoptions["full-logs"])) if "no-stealth" in cfgoptions: config.write("no-stealth={0}\n".format( cfgoptions["no-stealth"])) if "norefer" not in cfgoptions: config.write("referrer={0}\n".format( get_referrer_url(interest))) if firstproc: Process.first_process = False if thread_id or self.suspended: log.debug("Using QueueUserAPC injection.") else: log.debug("Using CreateRemoteThread injection.") orig_bin_name = "" bit_str = "" if is_64bit: orig_bin_name = "loader_x64.exe" bit_str = "64-bit" else: orig_bin_name = "loader.exe" bit_str = "32-bit" bin_name = randomize_bin(os.path.join("bin", orig_bin_name), "exe") if os.path.exists(bin_name): ret = subprocess.call( [bin_name, "inject", str(self.pid), str(thread_id), dll]) if ret != 0: if ret == 1: log.info("Injected into suspended %s process with pid %d", bit_str, self.pid) else: log.error( "Unable to inject into %s process with pid %d, error: %d", bit_str, self.pid, ret) return False else: return True else: log.error( "Please place the %s binary from cuckoomon into analyzer/windows/bin in order to analyze %s binaries.", os.path.basename(bin_name), bit_str) return False
def __init__(self, options={}, analyzer=None): self.config = Config(cfg="analysis.conf") self.proc = None
def __init__(self): self.config = Config(cfg="analysis.conf") self.proc = None
def __init__(self): self.config = Config(cfg="analysis.conf") self.fallback_strace = False
# Copyright (C) 2014-2016 Cuckoo Foundation. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org # See the file 'docs/LICENSE' for copying permission. import logging import os import socket import time from pathlib import Path from lib.core.config import Config config = Config(cfg="analysis.conf") log = logging.getLogger(__name__) BUFSIZE = 1024 * 1024 def upload_to_host(file_path, dump_path, pids="", ppids="", metadata="", category="", duplicated=False): nc = None if not os.path.exists(file_path): log.warning("File %s doesn't exist anymore", file_path) return file_size = Path(file_path).stat().st_size log.info("File %s size is %d, Max size: %s", file_path, file_size,
def inject(self, dll=os.path.join("dll", "cuckoomon.dll"), apc=False): """Cuckoo DLL injection. @param dll: Cuckoo DLL path. @param apc: APC use. """ if self.pid == 0: log.warning("No valid pid specified, injection aborted") return False if not self.is_alive(): log.warning("The process with pid %d is not alive, injection " "aborted" % self.pid) return False dll = randomize_dll(dll) if not dll or not os.path.exists(dll): log.warning("No valid DLL specified to be injected in process " "with pid %d, injection aborted" % self.pid) return False arg = KERNEL32.VirtualAllocEx(self.h_process, None, len(dll) + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE) if not arg: log.error("VirtualAllocEx failed when injecting process with " "pid %d, injection aborted (Error: %s)" % (self.pid, get_error_string(KERNEL32.GetLastError()))) return False bytes_written = c_int(0) if not KERNEL32.WriteProcessMemory(self.h_process, arg, dll + "\x00", len(dll) + 1, byref(bytes_written)): log.error("WriteProcessMemory failed when injecting process " "with pid %d, injection aborted (Error: %s)" % (self.pid, get_error_string(KERNEL32.GetLastError()))) return False kernel32_handle = KERNEL32.GetModuleHandleA("kernel32.dll") load_library = KERNEL32.GetProcAddress(kernel32_handle, "LoadLibraryA") config_path = os.path.join(os.getenv("TEMP"), "%s.ini" % self.pid) with open(config_path, "w") as config: cfg = Config("analysis.conf") config.write("host-ip={0}\n".format(cfg.ip)) config.write("host-port={0}\n".format(cfg.port)) config.write("pipe={0}\n".format(PIPE)) config.write("results={0}\n".format(PATHS["root"])) config.write("analyzer={0}\n".format(os.getcwd())) config.write("first-process={0}\n".format(Process.first_process)) Process.first_process = False if apc or self.suspended: log.info("Using QueueUserAPC injection") if not self.h_thread: log.info("No valid thread handle specified for injecting " "process with pid %d, injection aborted" % self.pid) return False if not KERNEL32.QueueUserAPC(load_library, self.h_thread, arg): log.error("QueueUserAPC failed when injecting process " "with pid %d (Error: %s)" % (self.pid, get_error_string(KERNEL32.GetLastError()))) return False log.info("Successfully injected process with pid %d" % self.pid) else: event_name = "CuckooEvent%d" % self.pid self.event_handle = KERNEL32.CreateEventA(None, False, False, event_name) if not self.event_handle: log.warning("Unable to create notify event..") return False log.info("Using CreateRemoteThread injection") new_thread_id = c_ulong(0) thread_handle = KERNEL32.CreateRemoteThread(self.h_process, None, 0, load_library, arg, 0, byref(new_thread_id)) if not thread_handle: log.error("CreateRemoteThread failed when injecting " + "process with pid %d (Error: %s)" % (self.pid, get_error_string(KERNEL32.GetLastError()))) KERNEL32.CloseHandle(self.event_handle) self.event_handle = None return False else: KERNEL32.CloseHandle(thread_handle) return True
def prepare(self): """Prepare env for analysis.""" global DEFAULT_DLL global SERVICES_PID global HIDE_PIDS # Get SeDebugPrivilege for the Python process. It will be needed in # order to perform the injections. grant_debug_privilege() # randomize cuckoomon DLL and loader executable names copy("dll\\cuckoomon.dll", CUCKOOMON32_NAME) copy("dll\\cuckoomon_x64.dll", CUCKOOMON64_NAME) copy("bin\\loader.exe", LOADER32_NAME) copy("bin\\loader_x64.exe", LOADER64_NAME) # Create the folders used for storing the results. create_folders() add_protected_path(os.getcwd()) add_protected_path(PATHS["root"]) # Initialize logging. init_logging() # Parse the analysis configuration file generated by the agent. self.config = Config(cfg="analysis.conf") # Set virtual machine clock. clock = datetime.strptime(self.config.clock, "%Y%m%dT%H:%M:%S") # Setting date and time. # NOTE: Windows system has only localized commands with date format # following localization settings, so these commands for english date # format cannot work in other localizations. # In addition DATE and TIME commands are blocking if an incorrect # syntax is provided, so an echo trick is used to bypass the input # request and not block analysis. thedate = clock.strftime("%m-%d-%y") thetime = clock.strftime("%H:%M:%S") os.system("echo:|date {0}".format(thedate)) os.system("echo:|time {0}".format(thetime)) log.info("Date set to: {0}, time set to: {1}".format(thedate, thetime)) # Set the default DLL to be used by the PipeHandler. DEFAULT_DLL = self.config.get_options().get("dll") # get PID for services.exe for monitoring services svcpid = self.pids_from_process_name_list(["services.exe"]) if svcpid: SERVICES_PID = svcpid[0] protected_procname_list = [ "vmwareuser.exe", "vmwareservice.exe", "vboxservice.exe", "vboxtray.exe", "sandboxiedcomlaunch.exe", "sandboxierpcss.exe", "procmon.exe", "regmon.exe", "filemon.exe", "wireshark.exe", "netmon.exe", "prl_tools_service.exe", "prl_tools.exe", "prl_cc.exe", "sharedintapp.exe", "vmtoolsd.exe", "vmsrvc.exe", "python.exe", "perl.exe", ] HIDE_PIDS = set( self.pids_from_process_name_list(protected_procname_list)) # Initialize and start the Pipe Servers. This is going to be used for # communicating with the injected and monitored processes. for x in xrange(self.PIPE_SERVER_COUNT): self.pipes[x] = PipeServer(self.config) self.pipes[x].daemon = True self.pipes[x].start() # We update the target according to its category. If it's a file, then # we store the path. if self.config.category == "file": self.target = os.path.join(os.environ["TEMP"] + os.sep, str(self.config.file_name)) # If it's a URL, well.. we store the URL. else: self.target = self.config.target
def run(self): """Run handler. @return: operation status. """ data = "" response = "OK" wait = False proc = None # Read the data submitted to the Pipe Server. 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() # Debug, Regular, or Critical information from CuckooMon. if command.startswith("DEBUG:"): log.debug(command[6:]) elif command.startswith("INFO:"): log.info(command[5:]) elif command.startswith("CRITICAL:"): log.critical(command[9:]) # Parse the prefix for the received notification. # In case of GETPIDS we're gonna return the current process ID # and the process ID of our parent process (agent.py). elif command == "GETPIDS": response = struct.pack("II", PID, PPID) # When analyzing we don't want to hook all functions, as we're # having some stability issues with regards to webbrowsers. elif command == "HOOKDLLS": is_url = Config(cfg="analysis.conf").category != "file" url_dlls = "ntdll", "kernel32" def hookdll_encode(names): # We have to encode each dll name as unicode string # with length 16. names = [name + "\x00" * (16-len(name)) for name in names] f = lambda s: "".join(ch + "\x00" for ch in s) return "".join(f(name) for name in names) # If this sample is not a URL, then we don't want to limit # any API hooks (at least for now), so we write a null-byte # which indicates that all DLLs should be hooked. if not is_url: response = "\x00" else: response = hookdll_encode(url_dlls) # In case of PID, the client is trying to notify the creation of # a new process to be injected and monitored. elif command.startswith("PROCESS:"): # We acquire the process lock in order to prevent the analyzer # to terminate the analysis while we are operating on the new # process. PROCESS_LOCK.acquire() # Set the current DLL to the default one provided # at submission. dll = DEFAULT_DLL # We parse the process ID. data = command[8:] process_id = thread_id = None if not "," in data: if data.isdigit(): process_id = int(data) elif data.count(",") == 2: process_id, param = data.split(",") thread_id = None if process_id.isdigit(): process_id = int(process_id) else: process_id = None if param.isdigit(): thread_id = int(param) else: # XXX: Expect a new DLL as a message parameter? if isinstance(param, str): dll = param if process_id: if process_id not in (PID, PPID): # We inject the process only if it's not being # monitored already, otherwise we would generated # polluted logs. if process_id not in PROCESS_LIST: # Open the process and inject the DLL. # Hope it enjoys it. proc = Process(pid=process_id, thread_id=thread_id) filepath = proc.get_filepath() filename = os.path.basename(filepath) log.info("Announced process name: %s", filename) if not protected_filename(filename): # Add the new process ID to the list of # monitored processes. add_pids(process_id) # If we have both pid and tid, then we can use # apc to inject. if process_id and thread_id: proc.inject(dll, apc=True) else: # We inject using CreateRemoteThread, this # needs the waiting in order to make sure # no race conditions occur. proc.inject(dll) wait = True log.info("Successfully injected process with " "pid %s", proc.pid) else: log.warning("Received request to inject Cuckoo " "processes, skip") # Once we're done operating on the processes list, we release # the lock. PROCESS_LOCK.release() # In case of FILE_NEW, the client is trying to notify the creation # of a new file. elif command.startswith("FILE_NEW:"): # We extract the file path. file_path = command[9:].decode("utf-8") # We add the file to the list. add_file(file_path) # In case of FILE_DEL, the client is trying to notify an ongoing # deletion of an existing file, therefore we need to dump it # straight away. elif command.startswith("FILE_DEL:"): # Extract the file path. file_path = command[9:].decode("utf-8") # Dump the file straight away. del_file(file_path) elif command.startswith("FILE_MOVE:"): # Syntax = "FILE_MOVE:old_file_path::new_file_path". if "::" in command[10:]: old_fname, new_fname = command[10:].split("::", 1) move_file(old_fname.decode("utf-8"), new_fname.decode("utf-8")) KERNEL32.WriteFile(self.h_pipe, create_string_buffer(response), len(response), byref(bytes_read), None) KERNEL32.CloseHandle(self.h_pipe) # We wait until cuckoomon reports back. if wait: proc.wait() if proc: proc.close() return True
def __init__(self, proto=""): config = Config(cfg="analysis.conf") self.hostip, self.hostport = config.ip, config.port self.sock = None self.proto = proto self.connected = False
def debug_inject(self, dll=None, interest=None, childprocess=False, nosleepskip=False): """CAPE DLL debugger injection. @param dll: CAPE DLL debugger path. @param interest: path to file of interest, handed to cuckoomon config """ global LOGSERVER_POOL if not self.pid: log.warning("No valid pid specified, injection aborted") return False thread_id = 0 if self.thread_id: thread_id = self.thread_id if not self.is_alive(): log.warning( "The process with pid %s is not alive, " "injection aborted", self.pid) return False is_64bit = self.is_64bit() if not dll: log.debug("No debugger DLL has been specified for injection") if is_64bit: dll = CUCKOOMON64_NAME else: dll = CUCKOOMON32_NAME else: dll = os.path.join("dll", dll) log.info("DLL to inject is %s", dll) dll = os.path.join(os.getcwd(), dll) if not dll or not os.path.exists(dll): log.warning( "No valid DLL specified to be injected in process " "with pid %d, injection aborted.", self.pid) return False config_path = "C:\\%s.ini" % self.pid with open(config_path, "w") as config: cfg = Config("analysis.conf") cfgoptions = cfg.get_options() # start the logserver for this monitored process logserver_path = LOGSERVER_PREFIX + str(self.pid) if logserver_path not in LOGSERVER_POOL: LOGSERVER_POOL[logserver_path] = LogServer( cfg.ip, cfg.port, logserver_path) Process.process_num += 1 firstproc = Process.process_num == 1 config.write("host-ip={0}\n".format(cfg.ip)) config.write("host-port={0}\n".format(cfg.port)) config.write("pipe={0}\n".format(PIPE)) config.write("logserver={0}\n".format(logserver_path)) config.write("results={0}\n".format(PATHS["root"])) config.write("analyzer={0}\n".format(os.getcwd())) config.write( "first-process={0}\n".format("1" if firstproc else "0")) config.write("startup-time={0}\n".format(Process.startup_time)) config.write("file-of-interest={0}\n".format(interest)) config.write("shutdown-mutex={0}\n".format(SHUTDOWN_MUTEX)) config.write("terminate-event={0}{1}\n".format( TERMINATE_EVENT, self.pid)) if nosleepskip or ("force-sleepskip" not in cfgoptions and len(interest) > 2 and interest[1] != ':' and interest[0] != '\\' and Process.process_num <= 2): config.write("force-sleepskip=0\n") if "norefer" not in cfgoptions and "referrer" not in cfgoptions: config.write("referrer={0}\n".format( get_referrer_url(interest))) simple_optnames = [ "force-sleepskip", "full-logs", "force-flush", "no-stealth", "buffer-max", "large-buffer-max", "serial", "sysvol_ctimelow", "sysvol_ctimehigh", "sys32_ctimelow", "sys32_ctimehigh", "debug", "disable_hook_content", "hook-type", "exclude-apis", "exclude-dlls", "referrer", ] for optname in simple_optnames: if optname in cfgoptions: config.write("{0}={1}\n".format(optname, cfgoptions[optname])) log.info("Option '%s' with value '%s' sent to monitor", optname, cfgoptions[optname]) if "procdump" in cfgoptions: config.write("procdump={0}\n".format(cfgoptions["procdump"])) if "import_reconstruction" in cfgoptions: config.write("import_reconstruction={0}\n".format( cfgoptions["import_reconstruction"])) if "CAPE_var1" in cfgoptions: config.write("CAPE_var1={0}\n".format(cfgoptions["CAPE_var1"])) if "CAPE_var2" in cfgoptions: config.write("CAPE_var2={0}\n".format(cfgoptions["CAPE_var2"])) if "CAPE_var3" in cfgoptions: config.write("CAPE_var3={0}\n".format(cfgoptions["CAPE_var3"])) if "CAPE_var4" in cfgoptions: config.write("CAPE_var4={0}\n".format(cfgoptions["CAPE_var4"])) orig_bin_name = "" bit_str = "" if is_64bit: orig_bin_name = LOADER64_NAME bit_str = "64-bit" else: orig_bin_name = LOADER32_NAME bit_str = "32-bit" bin_name = os.path.join(os.getcwd(), orig_bin_name) if os.path.exists(bin_name) == False: log.error( "Please place the %s binary from cuckoomon into analyzer/windows/bin in order to debug %s binaries.", os.path.basename(bin_name), bit_str) return False else: if childprocess == False: ret = subprocess.call([ bin_name, "debug_load", str(self.pid), str(thread_id), str(self.h_process), str(self.h_thread), dll ]) else: ret = subprocess.call([ bin_name, "debug", str(self.pid), str(thread_id), str(self.h_process), str(self.h_thread), dll ]) if ret != 0: if ret == 1: log.info( "Injected debugger DLL into suspended %s process with pid %d", bit_str, self.pid) else: log.error( "Unable to inject debugger DLL into %s process with pid %d, error: %d", bit_str, self.pid, ret) return False else: return True