1
2
3
4
5
6 import datetime
7 import io
8 import json
9 import os
10 import time
11 import socket
12 import logging
13 import requests
14 import xmlrpclib
15
16 from zipfile import ZipFile, ZIP_STORED
17
18 from lib.cuckoo.common.config import Config
19 from lib.cuckoo.common.constants import CUCKOO_ROOT
20 from lib.cuckoo.common.constants import CUCKOO_GUEST_PORT, CUCKOO_GUEST_INIT
21 from lib.cuckoo.common.constants import CUCKOO_GUEST_COMPLETED
22 from lib.cuckoo.common.constants import CUCKOO_GUEST_FAILED
23 from lib.cuckoo.common.exceptions import CuckooGuestError
24 from lib.cuckoo.common.utils import TimeoutServer
25 from lib.cuckoo.core.database import Database
26
27 log = logging.getLogger(__name__)
28 db = Database()
31 """Creates the Zip file that is sent to the Guest."""
32 t = time.time()
33
34 zip_data = io.BytesIO()
35 zip_file = ZipFile(zip_data, "w", ZIP_STORED)
36
37
38
39 root = os.path.join(CUCKOO_ROOT, "analyzer", platform)
40 root_len = len(os.path.abspath(root))
41
42 if not os.path.exists(root):
43 log.error("No valid analyzer found at path: %s", root)
44 raise CuckooGuestError(
45 "No valid analyzer found for %s platform!" % platform
46 )
47
48
49
50 for root, dirs, files in os.walk(root):
51 archive_root = os.path.abspath(root)[root_len:]
52 for name in files:
53 path = os.path.join(root, name)
54 archive_name = os.path.join(archive_root, name)
55 zip_file.write(path, archive_name)
56
57
58 if platform == "windows":
59 dirpath = os.path.join(CUCKOO_ROOT, "data", "monitor", monitor)
60
61
62
63 if os.path.isfile(dirpath):
64 monitor = os.path.basename(open(dirpath, "rb").read().strip())
65 dirpath = os.path.join(CUCKOO_ROOT, "data", "monitor", monitor)
66
67 for name in os.listdir(dirpath):
68 path = os.path.join(dirpath, name)
69 archive_name = os.path.join("/bin", name)
70 zip_file.write(path, archive_name)
71
72 zip_file.close()
73 data = zip_data.getvalue()
74
75 if time.time() - t > 10:
76 log.warning(
77 "It took more than 10 seconds to build the Analyzer Zip for the "
78 "Guest. This might be a serious performance penalty. Is your "
79 "analyzer/windows/ directory bloated with unnecessary files?"
80 )
81
82 return data
83
85 """Old and deprecated Guest Manager.
86
87 This class handles the communications with the old agent running in the
88 virtual machine.
89 """
90
91 - def __init__(self, vm_id, ip, platform, task_id):
106
107 - def wait(self, status):
108 """Waiting for status.
109 @param status: status.
110 @return: always True.
111 """
112 log.debug("%s: waiting for status 0x%.04x", self.id, status)
113
114 end = time.time() + self.timeout
115 self.server._set_timeout(self.timeout)
116
117 while db.guest_get_status(self.task_id) == "starting":
118
119 if time.time() > end:
120 raise CuckooGuestError("{0}: the guest initialization hit the "
121 "critical timeout, analysis "
122 "aborted.".format(self.id))
123
124 try:
125
126
127 if self.server.get_status() == status:
128 log.debug("%s: status ready", self.id)
129 break
130 except:
131 pass
132
133 log.debug("%s: not ready yet", self.id)
134 time.sleep(1)
135
136 self.server._set_timeout(None)
137 return True
138
140 """Upload analyzer to guest.
141 @return: operation status.
142 """
143 zip_data = analyzer_zipfile(self.platform, monitor)
144
145 log.debug(
146 "Uploading analyzer to guest (id=%s, ip=%s, monitor=%s, size=%d)",
147 self.id, self.ip, monitor, len(zip_data)
148 )
149
150
151
152 try:
153 self.server.add_analyzer(xmlrpclib.Binary(zip_data))
154 except socket.timeout:
155 raise CuckooGuestError("{0}: guest communication timeout: unable "
156 "to upload agent, check networking or try "
157 "to increase timeout".format(self.id))
158
160 """Start analysis.
161 @param options: options.
162 @return: operation status.
163 """
164
165
166
167 self.timeout = options["timeout"] + self.cfg.timeouts.critical
168
169 url = "http://{0}:{1}".format(self.ip, CUCKOO_GUEST_PORT)
170 self.server = TimeoutServer(url, allow_none=True,
171 timeout=self.timeout)
172
173 try:
174
175
176
177 self.wait(CUCKOO_GUEST_INIT)
178
179
180 self.upload_analyzer(monitor)
181
182
183
184 try:
185 self.server.add_config(options)
186 except:
187 raise CuckooGuestError("{0}: unable to upload config to "
188 "analysis machine".format(self.id))
189
190
191 if options["category"] == "file":
192 try:
193 file_data = open(options["target"], "rb").read()
194 except (IOError, OSError) as e:
195 raise CuckooGuestError("Unable to read {0}, error: "
196 "{1}".format(options["target"], e))
197
198 data = xmlrpclib.Binary(file_data)
199
200 try:
201 self.server.add_malware(data, options["file_name"])
202 except Exception as e:
203 raise CuckooGuestError("{0}: unable to upload malware to "
204 "analysis machine: {1}".format(self.id, e))
205
206
207 pid = self.server.execute()
208 log.debug("%s: analyzer started with PID %d", self.id, pid)
209
210
211 except (socket.timeout, socket.error):
212 raise CuckooGuestError("{0}: guest communication timeout, check "
213 "networking or try to increase "
214 "timeout".format(self.id))
215
253
255 """This class represents the new Guest Manager. It operates on the new
256 Cuckoo Agent which features a more abstract but more feature-rich API."""
257
258 - def __init__(self, vmid, ipaddr, platform, task_id, analysis_manager):
279
280 @property
282 return self.analysis_manager.aux
283
284 - def get(self, method, *args, **kwargs):
285 """Simple wrapper around requests.get()."""
286 url = "http://%s:%s%s" % (self.ipaddr, self.port, method)
287 session = requests.Session()
288 session.trust_env = False
289 session.proxies = None
290 return session.get(url, *args, **kwargs)
291
292 - def post(self, method, *args, **kwargs):
293 """Simple wrapper around requests.post()."""
294 url = "http://%s:%s%s" % (self.ipaddr, self.port, method)
295 session = requests.Session()
296 session.trust_env = False
297 session.proxies = None
298 return session.post(url, *args, **kwargs)
299
301 """Wait until the Virtual Machine is available for usage."""
302 end = time.time() + self.timeout
303
304 while db.guest_get_status(self.task_id) == "starting":
305 try:
306 socket.create_connection((self.ipaddr, self.port), 1).close()
307 break
308 except socket.timeout:
309 log.debug("%s: not ready yet", self.vmid)
310 except socket.error:
311 log.debug("%s: not ready yet", self.vmid)
312 time.sleep(1)
313
314 if time.time() > end:
315 raise CuckooGuestError(
316 "%s: the guest initialization hit the critical timeout, "
317 "analysis aborted." % self.vmid
318 )
319
321 """Query the environment of the Agent in the Virtual Machine."""
322 self.environ = self.get("/environ").json()["environ"]
323
325 """Determine the path of the analyzer. Basically creating a temporary
326 directory in the systemdrive, i.e., C:\\."""
327 systemdrive = "%s\\" % self.environ["SYSTEMDRIVE"]
328
329 r = self.post("/mkdtemp", data={"dirpath": systemdrive})
330 self.analyzer_path = r.json()["dirpath"]
331
333 """Upload the analyzer to the Virtual Machine."""
334 zip_data = analyzer_zipfile(self.platform, monitor)
335
336 log.debug("Uploading analyzer to guest (id=%s, ip=%s, monitor=%s)",
337 self.vmid, self.ipaddr, monitor)
338
339 self.determine_analyzer_path()
340 data = {
341 "dirpath": self.analyzer_path,
342 }
343 self.post("/extract", files={"zipfile": zip_data}, data=data)
344
346 """Upload the analysis.conf for this task to the Virtual Machine."""
347 config = [
348 "[analysis]",
349 ]
350 for key, value in options.items():
351
352 if isinstance(value, datetime.datetime):
353 config.append("%s = %s" % (key, value.strftime("%Y%m%dT%H:%M:%S")))
354 else:
355 config.append("%s = %s" % (key, value))
356
357 data = {
358 "filepath": os.path.join(self.analyzer_path, "analysis.conf"),
359 }
360 self.post("/store", files={"file": "\n".join(config)}, data=data)
361
363 """Start the analysis by uploading all required files.
364
365 @param options: the task options
366 @param monitor: identifier of the monitor to be used.
367 """
368 log.info("Starting analysis on guest (id=%s, ip=%s)",
369 self.vmid, self.ipaddr)
370
371 self.options = options
372 self.timeout = options["timeout"] + self.cfg.timeouts.critical
373
374
375 self.wait_available()
376
377
378
379 if db.guest_get_status(self.task_id) != "starting":
380 return
381
382
383
384 r = self.get("/")
385 if r.status_code == 501:
386
387
388
389
390 self.is_old = True
391 self.aux.callback("legacy_agent")
392 self.old.start_analysis(options, monitor)
393 return
394
395 if r.status_code != 200:
396 log.critical(
397 "While trying to determine the Agent version that your VM is "
398 "running we retrieved an unexpected HTTP status code: %s. If "
399 "this is a false positive, please report this issue to the "
400 "Cuckoo Developers. HTTP response headers: %s",
401 r.status_code, json.dumps(dict(r.headers)),
402 )
403 db.guest_set_status(self.task_id, "failed")
404 return
405
406 try:
407 status = r.json()
408 version = status.get("version")
409 features = status.get("features", [])
410 except:
411 log.critical(
412 "We were unable to detect either the Old or New Agent in the "
413 "Guest VM, are you sure you have set it up correctly? Please "
414 "go through the documentation once more and otherwise inform "
415 "the Cuckoo Developers of your issue."
416 )
417 db.guest_set_status(self.task_id, "failed")
418 return
419
420 log.info("Guest is running Cuckoo Agent %s (id=%s, ip=%s)",
421 version, self.vmid, self.ipaddr)
422
423
424
425 if "pinning" in features:
426 self.get("/pinning")
427
428
429 self.query_environ()
430
431
432 self.upload_analyzer(monitor)
433
434
435 self.add_config(options)
436
437
438 self.aux.callback("prepare_guest")
439
440
441 if options["category"] == "file":
442 data = {
443 "filepath": os.path.join(self.environ["TEMP"], options["file_name"]),
444 }
445 files = {
446 "file": open(options["target"], "rb"),
447 }
448 self.post("/store", files=files, data=data)
449
450 if "execpy" in features:
451 data = {
452 "filepath": "%s\\analyzer.py" % self.analyzer_path,
453 "async": "yes",
454 "cwd": self.analyzer_path,
455 }
456 self.post("/execpy", data=data)
457 else:
458
459 data = {
460 "command": "C:\\Python27\\pythonw.exe %s\\analyzer.py" % self.analyzer_path,
461 "async": "yes",
462 "cwd": self.analyzer_path,
463 }
464 self.post("/execute", data=data)
465
467 if self.is_old:
468 self.old.wait_for_completion()
469 return
470
471 end = time.time() + self.timeout
472
473 while db.guest_get_status(self.task_id) == "running":
474 log.debug("%s: analysis still processing", self.vmid)
475
476 time.sleep(1)
477
478
479
480 if time.time() > end:
481 raise CuckooGuestError("The analysis hit the critical timeout, terminating.")
482
483 try:
484 status = self.get("/status", timeout=5).json()
485 except Exception as e:
486 log.info("Virtual Machine /status failed (%r)", e)
487
488
489
490 continue
491
492 if status["status"] == "complete":
493 log.info("%s: analysis completed successfully", self.vmid)
494 return
495 elif status["status"] == "exception":
496 log.info("%s: analysis caught an exception\n%s",
497 self.vmid, status["description"])
498 return
499
500 @property
502 """Currently the Physical machine manager is using GuestManager in
503 an incorrect way. This should be fixed up later but for now this
504 workaround will do."""
505 return self.old.server
506