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

Source Code for Module lib.api.process

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