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 set_terminate_event(self): """Sets the termination event for the process. """ if self.h_process == 0: self.open() event_name = TERMINATE_EVENT + str(self.pid) self.terminate_event_handle = KERNEL32.OpenEventW( EVENT_MODIFY_STATE, False, event_name) if self.terminate_event_handle: # make sure process is aware of the termination KERNEL32.SetEvent(self.terminate_event_handle) log.info("Terminate event set for process %d", self.pid) KERNEL32.CloseHandle(self.terminate_event_handle) else: log.error("Failed to open terminate event for pid %d", self.pid) return # recreate event for monitor 'reply' self.terminate_event_handle = KERNEL32.CreateEventW( 0, False, False, event_name) if not self.terminate_event_handle: log.error("Failed to create terminate-reply event for process %d", self.pid) return KERNEL32.WaitForSingleObject(self.terminate_event_handle, 5000) log.info("Termination confirmed for process %d", self.pid) KERNEL32.CloseHandle(self.terminate_event_handle) return
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 grant_debug_privilege(pid=None): """Grant debug privileges. @param pid: PID. @return: operation status. """ ADVAPI32.OpenProcessToken.argtypes = (wintypes.HANDLE, wintypes.DWORD, POINTER(wintypes.HANDLE)) ADVAPI32.LookupPrivilegeValueW.argtypes = (wintypes.LPWSTR, wintypes.LPWSTR, POINTER(LUID)) ADVAPI32.AdjustTokenPrivileges.argtypes = (wintypes.HANDLE, wintypes.BOOL, POINTER(TOKEN_PRIVILEGES), wintypes.DWORD, POINTER(TOKEN_PRIVILEGES), POINTER(wintypes.DWORD)) if pid is None: h_process = KERNEL32.GetCurrentProcess() else: h_process = KERNEL32.OpenProcess(PROCESS_ALL_ACCESS, False, pid) if not h_process: return False h_current_token = wintypes.HANDLE() if not ADVAPI32.OpenProcessToken(h_process, TOKEN_ALL_ACCESS, h_current_token): return False se_original_luid = LUID() if not ADVAPI32.LookupPrivilegeValueW(None, "SeDebugPrivilege", se_original_luid): return False luid_attributes = LUID_AND_ATTRIBUTES() luid_attributes.Luid = se_original_luid luid_attributes.Attributes = SE_PRIVILEGE_ENABLED token_privs = TOKEN_PRIVILEGES() token_privs.PrivilegeCount = 1 token_privs.Privileges = luid_attributes if not ADVAPI32.AdjustTokenPrivileges(h_current_token, False, token_privs, 0, None, None): return False KERNEL32.CloseHandle(h_current_token) KERNEL32.CloseHandle(h_process) 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 close(self): """Close any open handles. @return: operation status. """ ret = bool(self.h_process or self.h_thread) NT_SUCCESS = lambda val: val >= 0 if self.h_process: ret = NT_SUCCESS(KERNEL32.CloseHandle(self.h_process)) if self.h_thread: ret = NT_SUCCESS(KERNEL32.CloseHandle(self.h_thread)) return ret
def close(self): """Close any open handles. @return: operation status. """ ret = bool(self.h_process or self.h_thread) if self.h_process: ret = NT_SUCCESS(KERNEL32.CloseHandle(self.h_process)) self.h_process = None if self.h_thread: ret = NT_SUCCESS(KERNEL32.CloseHandle(self.h_thread)) self.h_thread = None return ret
def get_parent_pid(self): """Get the Parent Process ID.""" class PROCESS_BASIC_INFORMATION(Structure): _fields_ = [ ("ExitStatus", c_void_p), ("PebBaseAddress", c_void_p), ("AffinityMask", c_void_p), ("BasePriority", c_void_p), ("UniqueProcessId", c_void_p), ("InheritedFromUniqueProcessId", c_void_p), ] NT_SUCCESS = lambda val: val >= 0 pbi = PROCESS_BASIC_INFORMATION() size = c_int() # Set return value to signed 32bit integer. NTDLL.NtQueryInformationProcess.restype = c_int process_handle = self.open_process() ret = NTDLL.NtQueryInformationProcess( process_handle, 0, byref(pbi), sizeof(pbi), byref(size) ) KERNEL32.CloseHandle(process_handle) if NT_SUCCESS(ret) and size.value == sizeof(pbi): return pbi.InheritedFromUniqueProcessId
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): 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 get_filepath(self): """Get process image file path. @return: decoded file path. """ process_handle = self.open_process() NT_SUCCESS = lambda val: val >= 0 pbi = create_string_buffer(200) size = c_int() # Set return value to signed 32bit integer. NTDLL.NtQueryInformationProcess.restype = c_int ret = NTDLL.NtQueryInformationProcess(process_handle, 27, byref(pbi), sizeof(pbi), byref(size)) KERNEL32.CloseHandle(process_handle) if NT_SUCCESS(ret) and size.value > 8: try: fbuf = pbi.raw[8:] fbuf = fbuf[:fbuf.find("\x00\x00") + 1] return fbuf.decode("utf16", errors="ignore") except: return "" return ""
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 dump_memory(self): """Dump process memory. @return: operation status. """ if not self.pid: log.warning("No valid pid specified, memory dump aborted") return False if not self.is_alive(): log.warning("The process with pid %d is not alive, memory " "dump aborted", self.pid) return False self.get_system_info() page_size = self.system_info.dwPageSize min_addr = self.system_info.lpMinimumApplicationAddress max_addr = self.system_info.lpMaximumApplicationAddress mem = min_addr root = os.path.join(PATHS["memory"], str(int(time.time()))) if not os.path.exists(root): os.makedirs(root) # Now upload to host from the StringIO. nf = NetlogFile(os.path.join("memory", "%s.dmp" % str(self.pid))) process_handle = self.open_process() while mem < max_addr: mbi = MEMORY_BASIC_INFORMATION() count = c_ulong(0) if KERNEL32.VirtualQueryEx(process_handle, mem, byref(mbi), sizeof(mbi)) < sizeof(mbi): mem += page_size continue if mbi.State & MEM_COMMIT and \ mbi.Type & (MEM_IMAGE | MEM_MAPPED | MEM_PRIVATE): buf = create_string_buffer(mbi.RegionSize) if KERNEL32.ReadProcessMemory(process_handle, mem, buf, mbi.RegionSize, byref(count)): nf.sock.sendall(buf.raw) mem += mbi.RegionSize else: mem += page_size KERNEL32.CloseHandle(process_handle) nf.close() log.info("Memory dump of process with pid %d completed", self.pid) return True
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 exit_code(self): """Get process exit code. @return: exit code value. """ process_handle = self.open_process() exit_code = c_ulong(0) KERNEL32.GetExitCodeProcess(process_handle, byref(exit_code)) KERNEL32.CloseHandle(process_handle) return exit_code.value
def set_terminate_event(self): """Sets the termination event for the process. """ if self.h_process == 0: self.open() event_name = TERMINATE_EVENT + str(self.pid) event_handle = KERNEL32.OpenEventA(EVENT_MODIFY_STATE, False, event_name) if event_handle: # make sure process is aware of the termination KERNEL32.SetEvent(event_handle) KERNEL32.CloseHandle(event_handle) KERNEL32.Sleep(500)
def terminate(self): """Terminate process. @return: operation status. """ process_handle = self.open_process() ret = KERNEL32.TerminateProcess(process_handle, 1) KERNEL32.CloseHandle(process_handle) if ret: log.info("Successfully terminated process with pid %d.", self.pid) return True else: log.error("Failed to terminate process with pid %d.", self.pid) return False
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 run(self): """Run the pipe dispatcher.""" buf = create_string_buffer(BUFSIZE) bytes_written = c_uint() while self.do_run: message = self._read_message(buf) if not message: break response = self.dispatcher.dispatch(message) or "OK" KERNEL32.WriteFile(self.pipe_handle, response, len(response), byref(bytes_written), None) KERNEL32.CloseHandle(self.pipe_handle)
def wait(self): ret = True if self.event_handle: retval = KERNEL32.WaitForSingleObject(self.event_handle, 10000) if retval == WAIT_TIMEOUT: log.error( "Timeout waiting for cuckoomon to initialize in pid %d", self.pid) ret = False else: log.info("Successfully injected process with pid %d", self.pid) KERNEL32.CloseHandle(self.event_handle) self.event_handle = None return ret
def run(self): """Run the pipe dispatcher.""" buf = create_string_buffer(BUFSIZE) bytes_written = c_uint() #log.error(" [*] Thunder pipe.py@PipeDispatcher@run pipe_handle: [%s]" % (self.pipe_handle, )) while self.do_run: message = self._read_message(buf) if not message: #log.error(" [*] Thunder pipe.py@PipeDispatcher@run NOTMESSAGE") break response = self.dispatcher.dispatch(message) or "OK" KERNEL32.WriteFile(self.pipe_handle, response, len(response), byref(bytes_written), None) KERNEL32.CloseHandle(self.pipe_handle)
def set_terminate_event(self): """Sets the termination event for the process. """ if self.h_process == 0: self.open() event_name = TERMINATE_EVENT + str(self.pid) event_handle = KERNEL32.OpenEventA(EVENT_MODIFY_STATE, False, event_name) if event_handle: # make sure process is aware of the termination KERNEL32.SetEvent(event_handle) if KERNEL32.WaitForSingleObject(event_handle, 5000) == 0x00000080: #WAIT_ABANDONED log.error("Wait for reply to terminare_event timed out for pid %d", self.pid) else: log.info("Successfully received reply to terminare_event, pid %d", self.pid) KERNEL32.CloseHandle(event_handle) else: log.error("Failed to open terminate event for pid %d", self.pid)
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 get_parent_pid(self): """Get the Parent Process ID.""" process_handle = self.open_process() NT_SUCCESS = lambda val: val >= 0 pbi = (c_int * 6)() size = c_int() # Set return value to signed 32bit integer. NTDLL.NtQueryInformationProcess.restype = c_int ret = NTDLL.NtQueryInformationProcess(process_handle, 0, byref(pbi), sizeof(pbi), byref(size)) KERNEL32.CloseHandle(process_handle) if NT_SUCCESS(ret) and size.value == sizeof(pbi): return pbi[5] return None
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 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 __del__(self): """Close open handles.""" if self.h_process and self.h_process != KERNEL32.GetCurrentProcess(): KERNEL32.CloseHandle(self.h_process) if self.h_thread: KERNEL32.CloseHandle(self.h_thread)
def wait(self): if self.event_handle: KERNEL32.WaitForSingleObject(self.event_handle, 10000) KERNEL32.CloseHandle(self.event_handle) self.event_handle = None return True
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)