1
2
3
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
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
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
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
66 """Get system information."""
67 self.system_info = SYSTEM_INFO()
68 KERNEL32.GetSystemInfo(byref(self.system_info))
69
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
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
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
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
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
150 """Process is alive?
151 @return: process status.
152 """
153 return self.exit_code() == STILL_ACTIVE
154
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
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
194 startup_info.dwFlags = 1
195
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
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
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
324 if Process.first_process:
325
326
327
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
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
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
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