def invoke(self, ctlcode, value, outlength=0x1000): device_handle = KERNEL32.CreateFileA( "\\\\.\\%s" % self.pipepath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None) % 2**32 if device_handle == 0xffffffff: # Only report an error if the error is not "name not found", # indicating that no kernel analysis is currently taking place. if KERNEL32.GetLastError() != 2: log.warning("Error opening handle to driver (%s): %d!", driver_name, KERNEL32.GetLastError()) return False out = ctypes.create_string_buffer(outlength) length = ctypes.c_uint() ret = KERNEL32.DeviceIoControl(device_handle, ctlcode, value, len(value), out, ctypes.sizeof(out), ctypes.byref(length), None) KERNEL32.CloseHandle(device_handle) if not ret: log.warning("Error performing ioctl (0x%08x): %d!", ctlcode, KERNEL32.GetLastError()) return False return out.raw[:length.value]
def run(self): buf = create_string_buffer(BUFSIZE) bytes_read = c_uint() pid = c_uint() # The first four bytes indicate the process identifier. In case the # pipe handle is closed in an unknown way, reopening one and # specifying the same process identifier will reuse the same socket, # thus making it look like as if it was never closed in the first # place. success = KERNEL32.ReadFile(self.pipe_handle, byref(pid), sizeof(pid), byref(bytes_read), None) if not success or bytes_read.value != sizeof(pid): log.warning("Unable to read the process identifier of this " "log pipe instance.") KERNEL32.CloseHandle(self.pipe_handle) return if self.active.get(pid.value): log.warning("A second log pipe handler for an active process is " "being requested, denying request.") KERNEL32.CloseHandle(self.pipe_handle) return if pid.value: if pid.value not in self.sockets: self.sockets[pid.value] = (socket.create_connection( self.destination)) sock = self.sockets[pid.value] self.active[pid.value] = True else: sock = socket.create_connection(self.destination) while True: success = KERNEL32.ReadFile(self.pipe_handle, byref(buf), sizeof(buf), byref(bytes_read), None) #log.error(" [*] Thunder pipe.py@PipeForwarder@run buffer: Len[%d]", bytes_read.value) #import traceback #log.error(" [*] Thunder pipe.py: [%s]", str(traceback.format_stack())) #log.error(buf[:bytes_read.value]) if success or KERNEL32.GetLastError() == ERROR_MORE_DATA: #log.info("Sending data") sock.sendall(buf.raw[:bytes_read.value]) # If we get the broken pipe error then this pipe connection has # been terminated for one reason or another. So break from the # loop and make the socket "inactive", that is, another pipe # connection can in theory pick it up. (This will only happen in # cases where malware for some reason broke our pipe connection). elif KERNEL32.GetLastError() == ERROR_BROKEN_PIPE: break else: log.warning("The log pipe handler has failed, last error %d.", KERNEL32.GetLastError()) break if pid.value: self.active[pid.value] = False
def old_inject(self, dll, apc): 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") if apc or self.suspended: 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 else: 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())) return False else: KERNEL32.CloseHandle(thread_handle) return True
def run(self): buf = create_string_buffer(BUFSIZE) bytes_read = c_uint() pid = c_uint() # The first four bytes indicate the process identifier. In case the # pipe handle is closed in an unknown way, reopening one and # specifying the same process identifier will reuse the same socket, # thus making it look like as if it was never closed in the first # place. success = KERNEL32.ReadFile(self.pipe_handle, byref(pid), sizeof(pid), byref(bytes_read), None) if not success or bytes_read.value != sizeof(pid): log.warning("Unable to read the process identifier of this " "log pipe instance.") KERNEL32.CloseHandle(self.pipe_handle) return if self.active.get(pid.value): log.warning("A second log pipe handler for an active process is " "being requested, denying request.") KERNEL32.CloseHandle(self.pipe_handle) return if pid.value: self.active[pid.value] = True while True: success = KERNEL32.ReadFile(self.pipe_handle, byref(buf), sizeof(buf), byref(bytes_read), None) if success or KERNEL32.GetLastError() == ERROR_MORE_DATA: d = os.path.join(os.getcwd(), 'stuff', 'logs') if not os.path.exists(d): os.makedirs(d) fn = os.path.join(d, '{0}.bson'.format(str(pid.value))) with open(fn, 'ab') as fa: fa.write(buf.raw[:bytes_read.value]) elif KERNEL32.GetLastError() == ERROR_BROKEN_PIPE: break else: log.warning("The log pipe handler has failed, last error %d.", KERNEL32.GetLastError()) break if pid.value: self.active[pid.value] = False return
def run(self): """Create and run Log Server. @return: operation status. """ try: while self.do_run: # Create the Named Pipe. # If we receive a connection to the pipe, we invoke the handler. if KERNEL32.ConnectNamedPipe(self.h_pipe, None) or KERNEL32.GetLastError( ) == ERROR_PIPE_CONNECTED: self.handle_logs() KERNEL32.CloseHandle(self.h_pipe) self.resultserver_socket.close() return True except Exception as e: print(e) self.resultserver_socket.close() if self.h_pipe != 0: KERNEL32.CloseHandle(self.h_pipe) error_exc = traceback.format_exc() log.exception(error_exc) return True
def run(self): """Create and run PIPE server. @return: operation status. """ while self.do_run: # Create the Named Pipe. 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 we receive a connection to the pipe, we invoke the handler. if KERNEL32.ConnectNamedPipe( h_pipe, None) or KERNEL32.GetLastError() == ERROR_PIPE_CONNECTED: handler = PipeHandler(h_pipe) handler.daemon = True handler.start() else: KERNEL32.CloseHandle(h_pipe) return True
def run(self): while self.do_run: flags = FILE_FLAG_WRITE_THROUGH if self.message: pipe_handle = KERNEL32.CreateNamedPipeA( self.pipe_name, PIPE_ACCESS_DUPLEX | flags, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, None) else: pipe_handle = KERNEL32.CreateNamedPipeA( self.pipe_name, PIPE_ACCESS_INBOUND | flags, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, BUFSIZE, 0, None) if pipe_handle == INVALID_HANDLE_VALUE: log.warning("Error opening logging pipe server.") continue if KERNEL32.ConnectNamedPipe(pipe_handle, None) or \ KERNEL32.GetLastError() == ERROR_PIPE_CONNECTED: handler = self.pipe_handler(pipe_handle, **self.kwargs) handler.daemon = True handler.start() else: KERNEL32.CloseHandle(pipe_handle)
def spCreateProcessW(application_name, command_line, process_attributes, thread_attributes, inherit_handles, creation_flags, environment, current_directory, startup_info): class STARTUPINFO(Structure): _fields_ = [ ("cb", c_uint), ("reserved1", c_void_p), ("desktop", c_void_p), ("title", c_void_p), ("unused1", c_uint * 7), ("flags", c_uint), ("show_window", c_uint16), ("reserved2", c_uint16), ("reserved3", c_void_p), ("std_input", c_void_p), ("std_output", c_void_p), ("std_error", c_void_p), ] class PROCESS_INFORMATION(Structure): _fields_ = [ ("process_handle", c_void_p), ("thread_handle", c_void_p), ("process_identifier", c_uint), ("thread_identifier", c_uint), ] class Handle(int): def Close(self): KERNEL32.CloseHandle(self) if environment: environment = "\x00".join("%s=%s" % (k, v) for k, v in environment.items()) + "\x00\x00" si = STARTUPINFO() si.cb = sizeof(STARTUPINFO) if startup_info: si.flags = startup_info.dwFlags si.show_window = startup_info.wShowWindow if si.flags & subprocess.STARTF_USESTDHANDLES: si.std_input = cast(int(startup_info.hStdInput), c_void_p) si.std_output = cast(int(startup_info.hStdOutput), c_void_p) si.std_error = cast(int(startup_info.hStdError), c_void_p) pi = PROCESS_INFORMATION() result = KERNEL32.CreateProcessW(application_name, command_line, None, None, inherit_handles, creation_flags, environment, current_directory, byref(si), byref(pi)) if not result: # TODO We'll just assume this is correct for now. raise WindowsError(KERNEL32.GetLastError()) return (Handle(pi.process_handle), Handle(pi.thread_handle), pi.process_identifier, pi.thread_identifier)
def execute(self, path, args=None, suspended=False, kernel_analysis=False): """Execute sample process. @param path: sample path. @param args: process args. @param suspended: is suspended. @return: operation status. """ if not os.access(path, os.X_OK): log.error( 'Unable to access file at path "%s", ' "execution aborted", path) return False startup_info = STARTUPINFO() startup_info.cb = sizeof(startup_info) # STARTF_USESHOWWINDOW startup_info.dwFlags = 1 # SW_SHOWNORMAL startup_info.wShowWindow = 1 process_info = PROCESS_INFORMATION() arguments = '"' + path + '" ' if args: arguments += args creation_flags = CREATE_NEW_CONSOLE if suspended: self.suspended = True creation_flags += CREATE_SUSPENDED created = KERNEL32.CreateProcessW(path, arguments, None, None, None, creation_flags, None, os.getenv("TEMP"), byref(startup_info), byref(process_info)) if created: self.pid = process_info.dwProcessId self.h_process = process_info.hProcess self.thread_id = process_info.dwThreadId self.h_thread = process_info.hThread log.info( 'Successfully executed process from path "%s" with ' 'arguments "%s" with pid %d', path, args or "", self.pid) if kernel_analysis: return self.kernel_analyze() return True else: log.error( 'Failed to execute process from path "%s" with ' 'arguments "%s" (Error: %s)', path, args, get_error_string(KERNEL32.GetLastError()), ) return False
def execute(self, path, args=None, suspended=False): """Execute sample process. @param path: sample path. @param args: process args. @param suspended: is suspended. @return: operation status. """ if not os.access(path, os.X_OK): log.error("Unable to access file at path \"%s\", " "execution aborted", path) return False startup_info = STARTUPINFO() startup_info.cb = sizeof(startup_info) process_info = PROCESS_INFORMATION() if args: arguments = "\"" + path + "\" " + args else: arguments = None creation_flags = CREATE_NEW_CONSOLE if suspended: self.suspended = True creation_flags += CREATE_SUSPENDED created = KERNEL32.CreateProcessA(path, arguments, None, None, None, creation_flags, None, os.getenv("TEMP"), byref(startup_info), byref(process_info)) if created: self.pid = process_info.dwProcessId self.h_process = process_info.hProcess self.thread_id = process_info.dwThreadId self.h_thread = process_info.hThread log.info("Successfully executed process from path \"%s\" with " "arguments \"%s\" with pid %d", path, args, self.pid) return True else: log.error("Failed to execute process from path \"%s\" with " "arguments \"%s\" (Error: %s)", path, args, get_error_string(KERNEL32.GetLastError())) return False
def _read_message(self, buf): """Reads a message.""" bytes_read = c_uint() ret = "" while True: success = KERNEL32.ReadFile(self.pipe_handle, byref(buf), sizeof(buf), byref(bytes_read), None) if KERNEL32.GetLastError() == ERROR_MORE_DATA: ret += buf.raw[:bytes_read.value] elif success: return ret + buf.raw[:bytes_read.value] else: return
def run(self): while self.do_run: # Create the Named Pipe. sd = SECURITY_DESCRIPTOR() sa = SECURITY_ATTRIBUTES() ADVAPI32.InitializeSecurityDescriptor(byref(sd), 1) ADVAPI32.SetSecurityDescriptorDacl(byref(sd), True, None, False) sa.nLength = sizeof(SECURITY_ATTRIBUTES) sa.bInheritHandle = False sa.lpSecurityDescriptor = addressof(sd) # flags = FILE_FLAG_WRITE_THROUGH if self.message: pipe_handle = KERNEL32.CreateNamedPipeW( self.pipe_name, PIPE_ACCESS_DUPLEX, # | flags, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, byref(sa), # None, ) else: pipe_handle = KERNEL32.CreateNamedPipeW( self.pipe_name, PIPE_ACCESS_INBOUND, # | flags, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, BUFSIZE, 0, byref(sa), # None, ) if pipe_handle == INVALID_HANDLE_VALUE: log.warning("Error opening logging pipe server") continue if KERNEL32.ConnectNamedPipe( pipe_handle, None) or KERNEL32.GetLastError() == ERROR_PIPE_CONNECTED: handler = self.pipe_handler(pipe_handle, **self.kwargs) handler.daemon = True handler.start() self.handlers.add(handler) else: KERNEL32.CloseHandle(pipe_handle)
def handle_logs(self): # Read the data submitted to the Log Server. while True: data = b"" while True: bytes_read = c_int(0) buf = create_string_buffer(LOGBUFSIZE) success = KERNEL32.ReadFile(self.h_pipe, buf, sizeof(buf), byref(bytes_read), None) data += buf.raw[:bytes_read.value] if success or KERNEL32.GetLastError() != ERROR_MORE_DATA: break # got an entire message, send it off to the resultserver if data: self.resultserver_socket.sendall(data)
def run(self): """Create and run PIPE server. @return: operation status. """ try: while self.do_run: # Create the Named Pipe. sd = SECURITY_DESCRIPTOR() sa = SECURITY_ATTRIBUTES() ADVAPI32.InitializeSecurityDescriptor(byref(sd), 1) ADVAPI32.SetSecurityDescriptorDacl(byref(sd), True, None, False) sa.nLength = sizeof(SECURITY_ATTRIBUTES) sa.bInheritHandle = False sa.lpSecurityDescriptor = addressof(sd) h_pipe = KERNEL32.CreateNamedPipeA(self.pipe_name, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, byref(sa)) if h_pipe == INVALID_HANDLE_VALUE: return False # If we receive a connection to the pipe, we invoke the handler. if KERNEL32.ConnectNamedPipe(h_pipe, None) or KERNEL32.GetLastError() == ERROR_PIPE_CONNECTED: handler = PipeHandler(h_pipe, self.config, self.options) handler.daemon = True handler.start() else: KERNEL32.CloseHandle(h_pipe) return True except Exception as e: error_exc = traceback.format_exc() log.exception(error_exc) return True
def execute(self, path, args=None, dll=None, free=False, curdir=None, source=None, mode=None, maximize=False, env=None, trigger=None): """Execute sample process. @param path: sample path. @param args: process args. @param dll: dll path. @param free: do not inject our monitor. @param curdir: current working directory. @param source: process identifier or process name which will become the parent process for the new process. @param mode: monitor mode - which functions to instrument. @param maximize: whether the GUI should be maximized. @param env: environment variables. @param trigger: trigger to indicate analysis start @return: operation status. """ if not os.access(path, os.X_OK): log.error("Unable to access file at path %r, execution aborted!", path) return False is32bit = self.is32bit(path=path) if not dll: if is32bit: dll = "monitor-x86.dll" else: dll = "monitor-x64.dll" dllpath = os.path.abspath(os.path.join("bin", dll)) if not os.path.exists(dllpath): log.warning("No valid DLL specified to be injected, " "injection aborted.") return False if source: if isinstance(source, (int, long)) or source.isdigit(): inject_is32bit = self.is32bit(pid=int(source)) else: inject_is32bit = self.is32bit(process_name=source) else: inject_is32bit = is32bit if inject_is32bit: inject_exe = os.path.join("bin", "inject-x86.exe") else: inject_exe = os.path.join("bin", "inject-x64.exe") argv = [ inject_exe, "--app", self.shortpath(path), "--only-start", ] if args: argv += ["--args", self._encode_args(args)] if curdir: argv += ["--curdir", self.shortpath(curdir)] if source: if isinstance(source, (int, long)) or source.isdigit(): argv += ["--from", "%s" % source] else: argv += ["--from-process", source] if maximize: argv += ["--maximize"] try: output = subprocess_checkoutput(argv, env) self.pid, self.tid = map(int, output.split()) except Exception: log.error( "Failed to execute process from path %r with " "arguments %r (Error: %s)", path, argv, get_error_string(KERNEL32.GetLastError())) return False # Report this PID to the kernel driver (if present). zer0m0n.addpid(self.pid) if is32bit: inject_exe = os.path.join("bin", "inject-x86.exe") else: inject_exe = os.path.join("bin", "inject-x64.exe") argv = [ inject_exe, "--resume-thread", "--pid", "%s" % self.pid, "--tid", "%s" % self.tid, ] if free: argv.append("--free") else: argv += [ "--apc", "--dll", dllpath, "--config", self.drop_config(mode=mode, trigger=trigger), ] try: subprocess_checkoutput(argv, env) except Exception: log.error( "Failed to execute process from path %r with " "arguments %r (Error: %s)", path, argv, get_error_string(KERNEL32.GetLastError())) return False log.info( "Successfully executed process from path %r with " "arguments %r and pid %d", path, args or "", self.pid) return True
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() # 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). if 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 len(data.split(",")) == 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 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 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 run(self): """Run handler. @return: operation status. """ global MONITORED_SERVICES global LASTINJECT_TIME 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": 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) # 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 protected_filename(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: # 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
def execute(self, path, args=None, dll=None, free=False, curdir=None, source=None, mode=None, maximize=False): """Execute sample process. @param path: sample path. @param args: process args. @param dll: dll path. @param free: do not inject our monitor. @param curdir: current working directory. @param source: process identifier or process name which will become the parent process for the new process. @param mode: monitor mode - which functions to instrument. @param maximize: whether the GUI should be maximized. @return: operation status. """ if not os.access(path, os.X_OK): log.error( "Unable to access file at path \"%s\", " "execution aborted", path) return False is32bit = self.is32bit(path=path) if not dll: if is32bit: dll = "monitor-x86.dll" else: dll = "monitor-x64.dll" dllpath = os.path.abspath(os.path.join("bin", dll)) if not os.path.exists(dllpath): log.warning("No valid DLL specified to be injected, " "injection aborted.") return False if is32bit: inject_exe = os.path.join("bin", "inject-x86.exe") else: inject_exe = os.path.join("bin", "inject-x64.exe") argv = [inject_exe, "--app", self.shortpath(path)] if args: argv += ["--args", self._encode_args(args)] if free: argv += ["--free"] else: argv += [ "--apc", "--dll", dllpath, "--config", self.drop_config(mode=mode) ] if curdir: argv += ["--curdir", self.shortpath(curdir)] if source: if isinstance(source, (int, long)) or source.isdigit(): argv += ["--from", "%s" % source] else: argv += ["--from-process", source] if maximize: argv += ["--maximize"] try: self.pid = int(subprocess.check_output(argv)) except Exception: log.error( "Failed to execute process from path %r with " "arguments %r (Error: %s)", path, argv, get_error_string(KERNEL32.GetLastError())) return False log.info( "Successfully executed process from path %r with " "arguments %r and pid %d", path, args or "", self.pid) return True
def run(self): """Run handler. @return: operation status. """ global MONITORED_SERVICES global MONITORED_WMI global MONITORED_DCOM global MONITORED_TASKSCHED global MONITORED_BITS global LASTINJECT_TIME global NUM_INJECTED 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) # 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) elif command.startswith("INTEROP:"): if not MONITORED_DCOM: MONITORED_DCOM = True dcom_pid = pid_from_service_name("DcomLaunch") if dcom_pid: servproc = Process(pid=dcom_pid,suspended=False) servproc.set_critical() filepath = servproc.get_filepath() servproc.inject(dll=DEFAULT_DLL, interest=filepath, nosleepskip=True) LASTINJECT_TIME = datetime.now() servproc.close() KERNEL32.Sleep(2000) elif command.startswith("WMI:"): if not MONITORED_WMI: MONITORED_WMI = True si = subprocess.STARTUPINFO() # STARTF_USESHOWWINDOW si.dwFlags = 1 # SW_HIDE si.wShowWindow = 0 log.info("Stopping WMI Service") subprocess.call(['net', 'stop', 'winmgmt', '/y'], startupinfo=si) log.info("Stopped WMI Service") subprocess.call("sc config winmgmt type= own", startupinfo=si) if not MONITORED_DCOM: MONITORED_DCOM = True dcom_pid = pid_from_service_name("DcomLaunch") if dcom_pid: servproc = Process(pid=dcom_pid,suspended=False) servproc.set_critical() filepath = servproc.get_filepath() servproc.inject(dll=DEFAULT_DLL, interest=filepath, nosleepskip=True) LASTINJECT_TIME = datetime.now() servproc.close() KERNEL32.Sleep(2000) log.info("Starting WMI Service") subprocess.call("net start winmgmt", startupinfo=si) log.info("Started WMI Service") wmi_pid = pid_from_service_name("winmgmt") if wmi_pid: servproc = Process(pid=wmi_pid,suspended=False) servproc.set_critical() filepath = servproc.get_filepath() servproc.inject(dll=DEFAULT_DLL, interest=filepath, nosleepskip=True) LASTINJECT_TIME = datetime.now() servproc.close() KERNEL32.Sleep(2000) elif command.startswith("TASKSCHED:"): if not MONITORED_TASKSCHED: MONITORED_TASKSCHED = True si = subprocess.STARTUPINFO() # STARTF_USESHOWWINDOW si.dwFlags = 1 # SW_HIDE si.wShowWindow = 0 log.info("Stopping Task Scheduler Service") subprocess.call(['net', 'stop', 'schedule', '/y'], startupinfo=si) log.info("Stopped Task Scheduler Service") subprocess.call("sc config schedule type= own", startupinfo=si) log.info("Starting Task Scheduler Service") subprocess.call("net start schedule", startupinfo=si) log.info("Started Task Scheduler Service") sched_pid = pid_from_service_name("schedule") if sched_pid: servproc = Process(pid=sched_pid,suspended=False) servproc.set_critical() filepath = servproc.get_filepath() servproc.inject(dll=DEFAULT_DLL, interest=filepath, nosleepskip=True) LASTINJECT_TIME = datetime.now() servproc.close() KERNEL32.Sleep(2000) elif command.startswith("BITS:"): if not MONITORED_BITS: MONITORED_BITS = True si = subprocess.STARTUPINFO() # STARTF_USESHOWWINDOW si.dwFlags = 1 # SW_HIDE si.wShowWindow = 0 log.info("Stopping BITS Service") subprocess.call(['net', 'stop', 'BITS', '/y'], startupinfo=si) log.info("Stopped BITS Service") subprocess.call("sc config BITS type= own", startupinfo=si) if not MONITORED_DCOM: MONITORED_DCOM = True dcom_pid = pid_from_service_name("DcomLaunch") if dcom_pid: servproc = Process(pid=dcom_pid,suspended=False) servproc.set_critical() filepath = servproc.get_filepath() servproc.inject(dll=DEFAULT_DLL, interest=filepath, nosleepskip=True) LASTINJECT_TIME = datetime.now() servproc.close() KERNEL32.Sleep(2000) log.info("Starting BITS Service") subprocess.call("net start BITS", startupinfo=si) log.info("Started BITS Service") bits_pid = pid_from_service_name("BITS") if bits_pid: servproc = Process(pid=bits_pid,suspended=False) servproc.set_critical() filepath = servproc.get_filepath() servproc.inject(dll=DEFAULT_DLL, interest=filepath, nosleepskip=True) LASTINJECT_TIME = datetime.now() servproc.close() KERNEL32.Sleep(2000) # 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() # STARTF_USESHOWWINDOW si.dwFlags = 1 # SW_HIDE si.wShowWindow = 0 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) servproc.set_critical() 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 attempted shutdowns/restarts -- flush logs for all monitored processes # additional handling can be added later elif command.startswith("SHUTDOWN:"): log.info("Received shutdown request") PROCESS_LOCK.acquire() for process_id in PROCESS_LIST: event_name = TERMINATE_EVENT + str(process_id) event_handle = KERNEL32.OpenEventA(EVENT_MODIFY_STATE, False, event_name) if event_handle: KERNEL32.SetEvent(event_handle) KERNEL32.CloseHandle(event_handle) if self.options.get("procmemdump"): p = Process(pid=process_id) p.dump_memory() dump_files() PROCESS_LOCK.release() # 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() NUM_INJECTED += 1 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() # if it's a URL analysis, provide the URL to all processes as # the "interest" -- this will allow cuckoomon to see in the # child browser process that a URL analysis is occurring if self.config.category == "file" or NUM_INJECTED > 1: interest = filepath else: interest = self.config.target is_64bit = proc.is_64bit() filename = os.path.basename(filepath) if not in_protected_path(filename) and proc.check_inject(): log.info("Announced %s process name: %s pid: %d", "64-bit" if is_64bit else "32-bit", filename, process_id) proc.inject(dll, interest) 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:"): FILES_LIST_LOCK.acquire() # We extract the file path. file_path = unicode(command[9:].decode("utf-8")) # We add the file to the list. add_file(file_path) FILES_LIST_LOCK.release() # 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:"): FILES_LIST_LOCK.acquire() # Extract the file path. file_path = unicode(command[9:].decode("utf-8")) # Dump the file straight away. del_file(file_path) FILES_LIST_LOCK.release() elif command.startswith("FILE_MOVE:"): FILES_LIST_LOCK.acquire() # 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"))) FILES_LIST_LOCK.release() 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 execute(self, path, args=None, suspended=False, kernel_analysis=False): """Execute sample process. @param path: sample path. @param args: process args. @param suspended: is suspended. @return: operation status. """ if not os.access(path, os.X_OK): log.error('Unable to access file at path "%s", execution aborted', path) return False startup_info = STARTUPINFO() startup_info.cb = sizeof(startup_info) # STARTF_USESHOWWINDOW startup_info.dwFlags = 1 # SW_SHOWNORMAL startup_info.wShowWindow = 1 process_info = PROCESS_INFORMATION() arguments = f'"{path}" ' if args: arguments += args creation_flags = CREATE_NEW_CONSOLE if suspended: self.suspended = True creation_flags += CREATE_SUSPENDED # Use the custom execution directory if provided, otherwise launch in the same location # where the sample resides (default %TEMP%) if "executiondir" in self.options.keys(): execution_directory = self.options["executiondir"] elif "curdir" in self.options.keys(): execution_directory = self.options["curdir"] else: execution_directory = os.getenv("TEMP") # Try to create the custom directories so that the execution path is deemed valid create_custom_folders(execution_directory) created = KERNEL32.CreateProcessW(path, arguments, None, None, None, creation_flags, None, execution_directory, byref(startup_info), byref(process_info)) if created: self.pid = process_info.dwProcessId self.h_process = process_info.hProcess self.thread_id = process_info.dwThreadId self.h_thread = process_info.hThread log.info( 'Successfully executed process from path "%s" with arguments "%s" with pid %d', path, args or "", self.pid) if kernel_analysis: return self.kernel_analyze() return True else: log.error( 'Failed to execute process from path "%s" with arguments "%s" (Error: %s)', path, args, get_error_string(KERNEL32.GetLastError()), ) return False
def run(self, waiting_time): # Open driver device self.hdevice = KERNEL32.CreateFileA(self.device_name, GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, 0, None) if self.hdevice == INVALID_HANDLE_VALUE: self.log.error("CreateFileA failed with error : 0x%x" % KERNEL32.GetLastError()) quit() self.log.info("Driver device file opened, handle = %x" % self.hdevice) #todo : build command line self.process = Process(self.command_line, self.log) self.process.create_suspended() self.pre_run() self.log.info("Target process handle value is 0x%x" % self.process.process_handle) self.thread_running = True thread = Thread(target=self.pipe_reader_thread, args=()) #Create an unpack event which will be signaled when the self.hUnpackEvent = KERNEL32.CreateEventA(NULL, 0, 0, "DaEvent") self.UserlandNotidyEvent = KERNEL32.CreateEventA( NULL, 0, 0, "UserlandNotidyEvent") #Struct sent to the driver MyPidStruct = PID_STRUCT() MyPidStruct.do_log = self.kernel_log MyPidStruct.RWEPolicy = self.rwe_policy MyPidStruct.InitialNXState = self.initial_nx_state MyPidStruct.UserlandNotidyEvent = self.UserlandNotidyEvent MyPidStruct.TargetProcessHandle = self.process.process_handle #Initiate driver's state and communication mecanisms BytesReturned = DWORD(0) success = KERNEL32.DeviceIoControl(self.hdevice, IOCTL_SETUP_STUFF, ctypes.byref(MyPidStruct), ctypes.sizeof(MyPidStruct), NULL, 0, ctypes.byref(BytesReturned), 0) if not (success): self.log.error("DeviceIoControl failed") raise UnpackerException("DeviceIoControl failed") thread.start() #Resume main process thtread self.process.resume() self.log.info("Main thread resumed") #Wait for unpacking to terminate r = KERNEL32.WaitForSingleObject(self.hUnpackEvent, self.max_unpack_time) if (r == WAIT_ABANDONED): self.log.error("Wait abandoned, something went wrong") raise UnpackerException("Wait abandoned, something went wrong") if (r == WAIT_TIMEOUT): self.log.info("Wait timed out") self.log.info("Thread suspended") if (r == WAIT_OBJECT_0): self.log.info("Event signaled") BytesReturned = DWORD(0) success = KERNEL32.DeviceIoControl(self.hdevice, IOCTL_SUSPEND_TRACKED, NULL, 0, NULL, 0, ctypes.byref(BytesReturned), 0) if not (success): self.log.error("DeviceIoControl failed") raise UnpackerException("DeviceIoControl failed") self.thread_running = False result = self.post_treatment() BytesReturned = DWORD(0) success = KERNEL32.DeviceIoControl(self.hdevice, IOCTL_UNTRACK_AND_RESUME_PROCESSES, NULL, 0, NULL, 0, ctypes.byref(BytesReturned), 0) if not (success): self.log.error("DeviceIoControl failed") raise UnpackerException("DeviceIoControl failed") BytesReturned = DWORD(0) success = KERNEL32.DeviceIoControl(self.hdevice, IOCTL_CLEANUP, NULL, 0, NULL, 0, ctypes.byref(BytesReturned), 0) if not (success): self.log.error("DeviceIoControl failed") raise UnpackerException("DeviceIoControl failed") KERNEL32.CloseHandle(self.hdevice) KERNEL32.CloseHandle(self.UserlandNotidyEvent) self.process.terminate() KERNEL32.ExitProcess(0)
def run(self): buf = create_string_buffer(BUFSIZE) bytes_read = c_uint() pid = c_uint() # The first four bytes indicate the process identifier. In case the # pipe handle is closed in an unknown way, reopening one and # specifying the same process identifier will reuse the same socket, # thus making it look like as if it was never closed in the first # place. success = KERNEL32.ReadFile(self.pipe_handle, byref(pid), sizeof(pid), byref(bytes_read), None) if not success or bytes_read.value != sizeof(pid): log.warning( "Unable to read the process identifier of this log pipe instance" ) KERNEL32.CloseHandle(self.pipe_handle) return if self.active.get(pid.value): log.warning( "A second log pipe handler for an active process is being requested, denying request" ) KERNEL32.CloseHandle(self.pipe_handle) return if pid.value: sock = self.sockets.get(pid.value) if not sock: sock = socket.create_connection(self.destination) self.sockets[pid.value] = sock self.active[pid.value] = True else: sock = socket.create_connection(self.destination) open_handles.add(sock) while self.do_run: success = KERNEL32.ReadFile(self.pipe_handle, byref(buf), sizeof(buf), byref(bytes_read), None) if success or KERNEL32.GetLastError() == ERROR_MORE_DATA: try: sock.sendall(buf.raw[:bytes_read.value]) except socket.error as e: if e.errno != errno.EBADF: log.warning("Failed socket operation: %s", e) break # If we get the broken pipe error then this pipe connection has # been terminated for one reason or another. So break from the # loop and make the socket "inactive", that is, another pipe # connection can in theory pick it up. (This will only happen in # cases where malware for some reason broke our pipe connection). elif KERNEL32.GetLastError() == ERROR_BROKEN_PIPE: break else: log.warning("The log pipe handler has failed, last error %d", KERNEL32.GetLastError()) break if pid.value: self.active[pid.value] = False