As explained in Analysis Packages, analysis packages are structured Python classes that describe how Cuckoo’s analyzer component should conduct the analysis procedure for a given file inside the guest environment.
As you already know, you can create your own packages and add them along with the default ones. Designing new packages is very easy and requires just a minimal understanding of programming and of the Python language.
As an example we’ll take a look at the default package for analyzing generic Windows executables (located at analyzer/windows/packages/exe.py):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from lib.common.abstracts import Package from lib.api.process import Process from lib.common.exceptions import CuckooPackageError class Exe(Package): """EXE analysis package.""" def start(self, path): free = self.options.get("free", False) args = self.options.get("arguments", None) suspended = True if free: suspended = False p = Process() if not p.execute(path=path, args=args, suspended=suspended): raise CuckooPackageError("Unable to execute initial process, analysis aborted") if not free and suspended: p.inject() p.resume() return p.pid else: return None def check(self): return True def finish(self): if self.options.get("procmemdump", False): for pid in self.pids: p = Process(pid=pid) p.dump_memory() return True
In this function you have to place all the initialization operations you want to run. This might include running the malware process, launching additional applications, taking memory snapshots and more.
This function is executed by Cuckoo every second while the malware is running. You can use this function to perform any kind of recurrent operation.
For example if in your analysis you are looking for just one specific indicator to be created (e.g. a file) you could place your condition in this function and if it returns False, the analysis will terminate straight away.
Think of it as “should the analysis continue or not?”.
For example:
def check(self):
if os.path.exists("C:\\config.bin"):
return False
else:
return True
This check() function will cause Cuckoo to immediately terminate the analysis whenever C:config.bin is created.
This function is simply called by Cuckoo before terminating the analysis and powering off the machine. By default, this function contains an optional feature to dump the process memory of all the monitored processes.
Every package have automatically access to a dictionary containing all user-specified options (see Submit an Analysis).
Such options are made available in the attribute self.options. For example let’s assume that the user specified the following string at submission:
foo=1,bar=2
The analysis package selected will have access to these values:
from lib.common.abstracts import Package
class Example(Package):
def start(self, path):
foo = self.options["foo"]
bar = self.options["bar"]
def check():
return True
def finish():
return True
These options can be used for anything you might need to configure inside your package.
The Process class provides access to different process-related features and functions. You can import it in your analysis packages with:
from lib.api.process import Process
You then initialize an instance with:
p = Process()
In case you want to open an existing process instead of creating a new one, you can specify multiple arguments:
- pid: PID of the process you want to operate on.
- h_process: handle of a process you want to operate on.
- thread_id: thread ID of a process you want to operate on.
- h_thread: handle of the thread of a process you want to operate on.
This class implements several methods that you can use in your own scripts.
Opens an handle to a running process. Returns True or False in case of success or failure of the operation.
Return type: | boolean |
---|
Example Usage:
1 2 3 | p = Process(pid=1234)
p.open()
handle = p.h_process
|
Returns the exit code of the opened process. If it wasn’t already done before, exit_code() will perform a call to open() to acquire an handle to the process.
Return type: | ulong |
---|
Example Usage:
1 2 | p = Process(pid=1234)
code = p.exit_code()
|
Calls exit_code() and verify if the returned code is STILL_ACTIVE, meaning that the given process is still running. Returns True or False.
Return type: | boolean |
---|
Example Usage:
1 2 3 | p = Process(pid=1234)
if p.is_alive():
print("Still running!")
|
Returns the PID of the parent process of the opened process. If it wasn’t already done before, get_parent_pid() will perform a call to open() to acquire an handle to the process.
Return type: | int |
---|
Example Usage:
1 2 | p = Process(pid=1234)
ppid = p.get_parent_pid()
|
Executes the file at the specified path. Returns True or False in case of success or failure of the operation.
Parameters: |
|
---|---|
Return type: | boolean |
Example Usage:
1 2 | p = Process()
p.execute(path="C:\\WINDOWS\\system32\\calc.exe", args="Something", suspended=True)
|
Resumes the opened process from a suspended state. Returns True or False in case of success or failure of the operation.
Return type: | boolean |
---|
Example Usage:
1 2 3 | p = Process()
p.execute(path="C:\\WINDOWS\\system32\\calc.exe", args="Something", suspended=True)
p.resume()
|
Terminates the opened process. Returns True or False in case of success or failure of the operaton.
Return type: | boolean |
---|
Example Usage:
1 2 3 4 5 | p = Process(pid=1234)
if p.terminate():
print("Process terminated!")
else:
print("Could not terminate the process!")
|
Injects a DLL (by default “dll/cuckoomon.dll”) into the opened process. Returns True or False in case of success or failure of the operation.
Parameters: |
|
---|---|
Return type: | boolean |
Example Usage:
1 2 3 4 | p = Process()
p.execute(path="C:\\WINDOWS\\system32\\calc.exe", args="Something", suspended=True)
p.inject()
p.resume()
|
Takes a snapshot of the given process’ memory space. Returns True or False in case of success or failure of the operation.
Return type: | boolean |
---|
Example Usage:
1 2 | p = Process(pid=1234)
p.dump_memory()
|