Package lib :: Package api :: Module process
[hide private]
[frames] | no frames]

Source Code for Module lib.api.process

  1  # Copyright (C) 2010-2015 Cuckoo Foundation. 
  2  # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org 
  3  # See the file 'docs/LICENSE' for copying permission. 
  4   
  5  import os 
  6  import logging 
  7  import random 
  8  from time import time 
  9  from ctypes import byref, c_ulong, create_string_buffer, c_int, sizeof 
 10  from shutil import copy 
 11   
 12  from lib.common.constants import PIPE, PATHS, SHUTDOWN_MUTEX 
 13  from lib.common.defines import KERNEL32, NTDLL, SYSTEM_INFO, STILL_ACTIVE 
 14  from lib.common.defines import THREAD_ALL_ACCESS, PROCESS_ALL_ACCESS 
 15  from lib.common.defines import STARTUPINFO, PROCESS_INFORMATION 
 16  from lib.common.defines import CREATE_NEW_CONSOLE, CREATE_SUSPENDED 
 17  from lib.common.defines import MEM_RESERVE, MEM_COMMIT, PAGE_READWRITE 
 18  from lib.common.defines import MEMORY_BASIC_INFORMATION 
 19  from lib.common.defines import WAIT_TIMEOUT 
 20  from lib.common.defines import MEM_IMAGE, MEM_MAPPED, MEM_PRIVATE 
 21  from lib.common.errors import get_error_string 
 22  from lib.common.rand import random_string 
 23  from lib.common.results import NetlogFile 
 24  from lib.core.config import Config 
 25   
 26  log = logging.getLogger(__name__) 
 27   
28 -def randomize_dll(dll_path):
29 """Randomize DLL name. 30 @return: new DLL path. 31 """ 32 new_dll_name = random_string(6) 33 new_dll_path = os.path.join(os.getcwd(), "dll", "{0}.dll".format(new_dll_name)) 34 35 try: 36 copy(dll_path, new_dll_path) 37 return new_dll_path 38 except: 39 return dll_path
40
41 -class Process:
42 """Windows process.""" 43 first_process = True 44
45 - def __init__(self, pid=0, h_process=0, thread_id=0, h_thread=0):
46 """@param pid: PID. 47 @param h_process: process handle. 48 @param thread_id: thread id. 49 @param h_thread: thread handle. 50 """ 51 self.pid = pid 52 self.h_process = h_process 53 self.thread_id = thread_id 54 self.h_thread = h_thread 55 self.suspended = False 56 self.event_handle = None
57
58 - def __del__(self):
59 """Close open handles.""" 60 if self.h_process and self.h_process != KERNEL32.GetCurrentProcess(): 61 KERNEL32.CloseHandle(self.h_process) 62 if self.h_thread: 63 KERNEL32.CloseHandle(self.h_thread)
64
65 - def get_system_info(self):
66 """Get system information.""" 67 self.system_info = SYSTEM_INFO() 68 KERNEL32.GetSystemInfo(byref(self.system_info))
69
70 - def open(self):
71 """Open a process and/or thread. 72 @return: operation status. 73 """ 74 ret = bool(self.pid or self.thread_id) 75 if self.pid and not self.h_process: 76 if self.pid == os.getpid(): 77 self.h_process = KERNEL32.GetCurrentProcess() 78 else: 79 self.h_process = KERNEL32.OpenProcess(PROCESS_ALL_ACCESS, 80 False, 81 self.pid) 82 ret = True 83 84 if self.thread_id and not self.h_thread: 85 self.h_thread = KERNEL32.OpenThread(THREAD_ALL_ACCESS, 86 False, 87 self.thread_id) 88 ret = True 89 return ret
90
91 - def close(self):
92 """Close any open handles. 93 @return: operation status. 94 """ 95 ret = bool(self.h_process or self.h_thread) 96 NT_SUCCESS = lambda val: val >= 0 97 98 if self.h_process: 99 ret = NT_SUCCESS(KERNEL32.CloseHandle(self.h_process)) 100 101 if self.h_thread: 102 ret = NT_SUCCESS(KERNEL32.CloseHandle(self.h_thread)) 103 104 return ret
105
106 - def exit_code(self):
107 """Get process exit code. 108 @return: exit code value. 109 """ 110 if not self.h_process: 111 self.open() 112 113 exit_code = c_ulong(0) 114 KERNEL32.GetExitCodeProcess(self.h_process, byref(exit_code)) 115 116 return exit_code.value
117
118 - def get_filepath(self):
119 """Get process image file path. 120 @return: decoded file path. 121 """ 122 if not self.h_process: 123 self.open() 124 125 NT_SUCCESS = lambda val: val >= 0 126 127 pbi = create_string_buffer(200) 128 size = c_int() 129 130 # Set return value to signed 32bit integer. 131 NTDLL.NtQueryInformationProcess.restype = c_int 132 133 ret = NTDLL.NtQueryInformationProcess(self.h_process, 134 27, 135 byref(pbi), 136 sizeof(pbi), 137 byref(size)) 138 139 if NT_SUCCESS(ret) and size.value > 8: 140 try: 141 fbuf = pbi.raw[8:] 142 fbuf = fbuf[:fbuf.find('\0\0')+1] 143 return fbuf.decode('utf16', errors="ignore") 144 except: 145 return "" 146 147 return ""
148
149 - def is_alive(self):
150 """Process is alive? 151 @return: process status. 152 """ 153 return self.exit_code() == STILL_ACTIVE
154
155 - def get_parent_pid(self):
156 """Get the Parent Process ID.""" 157 if not self.h_process: 158 self.open() 159 160 NT_SUCCESS = lambda val: val >= 0 161 162 pbi = (c_int * 6)() 163 size = c_int() 164 165 # Set return value to signed 32bit integer. 166 NTDLL.NtQueryInformationProcess.restype = c_int 167 168 ret = NTDLL.NtQueryInformationProcess(self.h_process, 169 0, 170 byref(pbi), 171 sizeof(pbi), 172 byref(size)) 173 174 if NT_SUCCESS(ret) and size.value == sizeof(pbi): 175 return pbi[5] 176 177 return None
178
179 - def execute(self, path, args=None, suspended=False):
180 """Execute sample process. 181 @param path: sample path. 182 @param args: process args. 183 @param suspended: is suspended. 184 @return: operation status. 185 """ 186 if not os.access(path, os.X_OK): 187 log.error("Unable to access file at path \"%s\", " 188 "execution aborted", path) 189 return False 190 191 startup_info = STARTUPINFO() 192 startup_info.cb = sizeof(startup_info) 193 # STARTF_USESHOWWINDOW 194 startup_info.dwFlags = 1 195 # SW_SHOWNORMAL 196 startup_info.wShowWindow = 1 197 process_info = PROCESS_INFORMATION() 198 199 arguments = "\"" + path + "\" " 200 if args: 201 arguments += args 202 203 creation_flags = CREATE_NEW_CONSOLE 204 if suspended: 205 self.suspended = True 206 creation_flags += CREATE_SUSPENDED 207 208 created = KERNEL32.CreateProcessA(path, 209 arguments, 210 None, 211 None, 212 None, 213 creation_flags, 214 None, 215 os.getenv("TEMP"), 216 byref(startup_info), 217 byref(process_info)) 218 219 if created: 220 self.pid = process_info.dwProcessId 221 self.h_process = process_info.hProcess 222 self.thread_id = process_info.dwThreadId 223 self.h_thread = process_info.hThread 224 log.info("Successfully executed process from path \"%s\" with " 225 "arguments \"%s\" with pid %d", path, args or "", self.pid) 226 return True 227 else: 228 log.error("Failed to execute process from path \"%s\" with " 229 "arguments \"%s\" (Error: %s)", path, args, 230 get_error_string(KERNEL32.GetLastError())) 231 return False
232
233 - def resume(self):
234 """Resume a suspended thread. 235 @return: operation status. 236 """ 237 if not self.suspended: 238 log.warning("The process with pid %d was not suspended at creation" 239 % self.pid) 240 return False 241 242 if not self.h_thread: 243 return False 244 245 KERNEL32.Sleep(2000) 246 247 if KERNEL32.ResumeThread(self.h_thread) != -1: 248 log.info("Successfully resumed process with pid %d", self.pid) 249 return True 250 else: 251 log.error("Failed to resume process with pid %d", self.pid) 252 return False
253
254 - def terminate(self):
255 """Terminate process. 256 @return: operation status. 257 """ 258 if self.h_process == 0: 259 self.open() 260 261 if KERNEL32.TerminateProcess(self.h_process, 1): 262 log.info("Successfully terminated process with pid %d.", self.pid) 263 return True 264 else: 265 log.error("Failed to terminate process with pid %d.", self.pid) 266 return False
267
268 - def inject(self, dll=None, apc=False):
269 """Cuckoo DLL injection. 270 @param dll: Cuckoo DLL path. 271 @param apc: APC use. 272 """ 273 if not self.pid: 274 log.warning("No valid pid specified, injection aborted") 275 return False 276 277 if not self.is_alive(): 278 log.warning("The process with pid %s is not alive, " 279 "injection aborted", self.pid) 280 return False 281 282 if not dll: 283 dll = "cuckoomon.dll" 284 285 dll = randomize_dll(os.path.join("dll", dll)) 286 287 if not dll or not os.path.exists(dll): 288 log.warning("No valid DLL specified to be injected in process " 289 "with pid %d, injection aborted.", self.pid) 290 return False 291 292 arg = KERNEL32.VirtualAllocEx(self.h_process, 293 None, 294 len(dll) + 1, 295 MEM_RESERVE | MEM_COMMIT, 296 PAGE_READWRITE) 297 298 if not arg: 299 log.error("VirtualAllocEx failed when injecting process with " 300 "pid %d, injection aborted (Error: %s)", 301 self.pid, get_error_string(KERNEL32.GetLastError())) 302 return False 303 304 bytes_written = c_int(0) 305 if not KERNEL32.WriteProcessMemory(self.h_process, 306 arg, 307 dll + "\x00", 308 len(dll) + 1, 309 byref(bytes_written)): 310 log.error("WriteProcessMemory failed when injecting process with " 311 "pid %d, injection aborted (Error: %s)", 312 self.pid, get_error_string(KERNEL32.GetLastError())) 313 return False 314 315 kernel32_handle = KERNEL32.GetModuleHandleA("kernel32.dll") 316 load_library = KERNEL32.GetProcAddress(kernel32_handle, "LoadLibraryA") 317 318 config_path = os.path.join(os.getenv("TEMP"), "%s.ini" % self.pid) 319 with open(config_path, "w") as config: 320 cfg = Config("analysis.conf") 321 cfgoptions = cfg.get_options() 322 323 # The first time we come up with a random startup-time. 324 if Process.first_process: 325 # This adds 1 up to 30 times of 20 minutes to the startup 326 # time of the process, therefore bypassing anti-vm checks 327 # which check whether the VM has only been up for <10 minutes. 328 Process.startup_time = random.randint(1, 30) * 20 * 60 * 1000 329 330 config.write("host-ip={0}\n".format(cfg.ip)) 331 config.write("host-port={0}\n".format(cfg.port)) 332 config.write("pipe={0}\n".format(PIPE)) 333 config.write("results={0}\n".format(PATHS["root"])) 334 config.write("analyzer={0}\n".format(os.getcwd())) 335 config.write("first-process={0}\n".format("1" if Process.first_process else "0")) 336 config.write("startup-time={0}\n".format(Process.startup_time)) 337 config.write("shutdown-mutex={0}\n".format(SHUTDOWN_MUTEX)) 338 config.write("force-sleepskip={0}\n".format(cfgoptions.get("force-sleepskip", "0"))) 339 340 Process.first_process = False 341 342 event_name = "CuckooEvent%d" % self.pid 343 self.event_handle = KERNEL32.CreateEventA(None, False, False, event_name) 344 if not self.event_handle: 345 log.warning("Unable to create notify event..") 346 return False 347 348 if apc or self.suspended: 349 log.debug("Using QueueUserAPC injection.") 350 if not self.h_thread: 351 log.info("No valid thread handle specified for injecting " 352 "process with pid %d, injection aborted.", self.pid) 353 self.event_handle = None 354 return False 355 356 if not KERNEL32.QueueUserAPC(load_library, self.h_thread, arg): 357 log.error("QueueUserAPC failed when injecting process with " 358 "pid %d (Error: %s)", 359 self.pid, get_error_string(KERNEL32.GetLastError())) 360 self.event_handle = None 361 return False 362 else: 363 log.debug("Using CreateRemoteThread injection.") 364 new_thread_id = c_ulong(0) 365 thread_handle = KERNEL32.CreateRemoteThread(self.h_process, 366 None, 367 0, 368 load_library, 369 arg, 370 0, 371 byref(new_thread_id)) 372 if not thread_handle: 373 log.error("CreateRemoteThread failed when injecting process " 374 "with pid %d (Error: %s)", 375 self.pid, get_error_string(KERNEL32.GetLastError())) 376 KERNEL32.CloseHandle(self.event_handle) 377 self.event_handle = None 378 return False 379 else: 380 KERNEL32.CloseHandle(thread_handle) 381 382 log.info("Successfully injected process with pid %d." % self.pid) 383 384 return True
385
386 - def wait(self):
387 ret = True 388 389 if self.event_handle: 390 retval = KERNEL32.WaitForSingleObject(self.event_handle, 10000) 391 if retval == WAIT_TIMEOUT: 392 log.error("Timeout waiting for cuckoomon to initialize in pid %d", self.pid) 393 ret = False 394 else: 395 log.info("Successfully injected process with pid %d", self.pid) 396 397 KERNEL32.CloseHandle(self.event_handle) 398 self.event_handle = None 399 400 return ret
401
402 - def dump_memory(self):
403 """Dump process memory. 404 @return: operation status. 405 """ 406 if not self.pid: 407 log.warning("No valid pid specified, memory dump aborted") 408 return False 409 410 if not self.is_alive(): 411 log.warning("The process with pid %d is not alive, memory " 412 "dump aborted", self.pid) 413 return False 414 415 self.get_system_info() 416 417 page_size = self.system_info.dwPageSize 418 min_addr = self.system_info.lpMinimumApplicationAddress 419 max_addr = self.system_info.lpMaximumApplicationAddress 420 mem = min_addr 421 422 root = os.path.join(PATHS["memory"], str(int(time()))) 423 424 if not os.path.exists(root): 425 os.makedirs(root) 426 427 # Now upload to host from the StringIO. 428 nf = NetlogFile(os.path.join("memory", "%s.dmp" % str(self.pid))) 429 430 while mem < max_addr: 431 mbi = MEMORY_BASIC_INFORMATION() 432 count = c_ulong(0) 433 434 if KERNEL32.VirtualQueryEx(self.h_process, 435 mem, 436 byref(mbi), 437 sizeof(mbi)) < sizeof(mbi): 438 mem += page_size 439 continue 440 441 if mbi.State & MEM_COMMIT and \ 442 mbi.Type & (MEM_IMAGE | MEM_MAPPED | MEM_PRIVATE): 443 buf = create_string_buffer(mbi.RegionSize) 444 if KERNEL32.ReadProcessMemory(self.h_process, 445 mem, 446 buf, 447 mbi.RegionSize, 448 byref(count)): 449 nf.sock.sendall(buf.raw) 450 mem += mbi.RegionSize 451 else: 452 mem += page_size 453 454 nf.close() 455 456 log.info("Memory dump of process with pid %d completed", self.pid) 457 458 return True
459