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
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
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
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
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
64 """Get system information."""
65 self.system_info = SYSTEM_INFO()
66 KERNEL32.GetSystemInfo(byref(self.system_info))
67
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
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
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
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
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
148 """Process is alive?
149 @return: process status.
150 """
151 return self.exit_code() == STILL_ACTIVE
152
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
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
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
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
318 if Process.first_process:
319
320
321
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
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
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
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