Skip to content

Commit b9a4f95

Browse files
authored
Adds private auxiliary module configuration option (#2345)
This adds a new `configure_from_data` method to analyzer.windows.lib.common.abstracts.Auxiliary. This optional method provides the ability to run private auxiliary-specific configuration code from `data.packages.<module_name>`. If an auxiliary module doesn't provide a `configure_from_data` method, it's logged but ignored.
1 parent da5694a commit b9a4f95

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

analyzer/windows/analyzer.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,21 @@ def run(self):
509509
except ImportError as e:
510510
log.warning('Unable to import the auxiliary module "%s": %s', name, e)
511511

512+
def configure_aux_from_data(instance):
513+
# Do auxiliary module configuration stored in 'data/auxiliary/<package_name>'
514+
_class = type(instance)
515+
try:
516+
log.debug("attempting to configure '%s' from data", _class.__name__)
517+
instance.configure_from_data()
518+
except ModuleNotFoundError:
519+
# let it go, not every module is configurable from data
520+
log.debug("module %s does not support data configuration, ignoring", _class.__name__)
521+
except ImportError as iexc:
522+
# let it go but emit a warning; assume a dependency is missing
523+
log.warning("configuration error for module %s: %s", _class.__name__, iexc)
524+
except Exception as exc:
525+
log.error("error configuring module %s: %s", _class.__name__, exc)
526+
512527
# Walk through the available auxiliary modules.
513528
aux_modules = []
514529

@@ -517,6 +532,7 @@ def run(self):
517532
aux = module(self.options, self.config)
518533
log.debug('Initialized auxiliary module "%s"', module.__name__)
519534
aux_modules.append(aux)
535+
configure_aux_from_data(aux)
520536
log.debug('Trying to start auxiliary module "%s"...', module.__module__)
521537
aux.start()
522538
except (NotImplementedError, AttributeError) as e:

analyzer/windows/lib/common/abstracts.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,29 @@ def add_pid(self, pid):
322322

323323
def del_pid(self, pid):
324324
pass
325+
326+
def configure_from_data(self):
327+
"""Do private auxiliary module-specific configuration.
328+
329+
Auxiliary modules can implement this method to perform pre-analysis
330+
configuration based on runtime data contained in "data/auxiliary/<package_name>".
331+
332+
This method raises:
333+
- ImportError when any exception occurs during import
334+
- AttributeError if the module configure function is invalid
335+
- ModuleNotFoundError if the module does not support configuration from data
336+
"""
337+
package_module_name = self.__class__.__module__.split(".")[-1]
338+
module_name = f"data.auxiliary.{package_module_name}"
339+
try:
340+
mod = importlib.import_module(module_name)
341+
except ModuleNotFoundError as exc:
342+
raise exc
343+
except Exception as exc:
344+
raise ImportError(f"error importing {module_name}: {exc}") from exc
345+
346+
spec = inspect.getfullargspec(mod.configure)
347+
if len(spec.args) != 1:
348+
err_msg = f"{module_name}.configure: expected 1 arguments, got {len(spec.args)}"
349+
raise AttributeError(err_msg)
350+
mod.configure(self)

docs/book/src/customization/auxiliary.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,52 @@ very end of the analysis process, before launching the processing and reporting
2727

2828
For example, an auxiliary module provided by default in CAPE is called *sniffer.py* and
2929
takes care of executing **tcpdump** in order to dump the generated network traffic.
30+
31+
Auxiliary Module Configuration
32+
==============================
33+
34+
Auxiliary modules can be "configured" before being started. This allows data to be added
35+
at runtime, whilst also allowing for the configuration to be stored separately from the
36+
CAPE python code.
37+
38+
Private Auxiliary Module Configuration
39+
--------------------------------------
40+
41+
Private auxiliary module configuration is stored outside the auxiliary class, in a module
42+
under the same name as the auxiliary module. This is useful when managing configuration
43+
of auxiliary modules separately if desired, for privacy reasons or otherwise.
44+
45+
Here is a configuration module example that installs some software prior to the auxiliary
46+
module starting:
47+
48+
.. code-block:: python
49+
:linenos:
50+
51+
# data/auxiliary/example.py
52+
import subprocess
53+
import logging
54+
from pathlib import Path
55+
56+
log = logging.getLogger(__name__)
57+
BIN_PATH = Path.cwd() / "bin"
58+
59+
60+
def configure(aux_instance):
61+
# here "example" refers to modules.auxiliary.example.Example
62+
if not aux_instance.enabled:
63+
return
64+
msi = aux_instance.options.get("example_msi")
65+
if not msi:
66+
return
67+
msi_path = BIN_PATH / msi
68+
if not msi_path.exists():
69+
log.warning("missing MSI %s", msi_path)
70+
return
71+
cmd = ["msiexec", "/i", msi_path, "/quiet"]
72+
try:
73+
log.info("Executing msi package...")
74+
subprocess.check_output(cmd)
75+
log.info("Installation succesful")
76+
except subprocess.CalledProcessError as exc:
77+
log.error("Installation failed: %s", exc)
78+
return

0 commit comments

Comments
 (0)