186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
|
|
import logging
|
|
from typing import Generator, Iterable, List, Tuple
|
|
|
|
from volatility3.framework import constants, interfaces, objects, renderers
|
|
from volatility3.framework.configuration import requirements
|
|
from volatility3.framework.interfaces import configuration
|
|
from volatility3.framework.objects.utility import array_to_string
|
|
from volatility3.framework.renderers import format_hints
|
|
from volatility3.framework.symbols import intermed
|
|
from volatility3.framework.symbols.windows.extensions import pe
|
|
from volatility3.plugins.windows import modules
|
|
|
|
vollog = logging.getLogger(__name__)
|
|
|
|
|
|
class Passphrase(interfaces.plugins.PluginInterface):
|
|
"""VeraCrypt/TrueCrypt cached passphrase finder (driver .data scan)."""
|
|
|
|
_version = (0, 1, 0)
|
|
_required_framework_version = (2, 5, 2)
|
|
|
|
@classmethod
|
|
def get_requirements(cls) -> List[configuration.RequirementInterface]:
|
|
return [
|
|
requirements.ModuleRequirement(
|
|
"kernel",
|
|
description="Windows kernel",
|
|
architectures=["Intel32", "Intel64"],
|
|
),
|
|
requirements.VersionRequirement(
|
|
name="modules", component=modules.Modules, version=(3, 0, 0)
|
|
),
|
|
requirements.IntRequirement(
|
|
name="min-length",
|
|
description="Minimum length of passphrases to identify",
|
|
default=5,
|
|
optional=True,
|
|
),
|
|
requirements.StringRequirement(
|
|
name="driver",
|
|
description=(
|
|
"Driver name substring to scan (case-insensitive), "
|
|
"e.g. 'veracrypt', 'veracrypt-x64.sys', 'truecrypt.sys'"
|
|
),
|
|
default="veracrypt",
|
|
optional=True,
|
|
),
|
|
]
|
|
|
|
def scan_module(
|
|
self, module_base: int, layer_name: str
|
|
) -> Generator[Tuple[int, str], None, None]:
|
|
pe_table_name = intermed.IntermediateSymbolTable.create(
|
|
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
|
|
)
|
|
dos_header: pe.IMAGE_DOS_HEADER = self.context.object(
|
|
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
|
|
layer_name,
|
|
module_base,
|
|
)
|
|
|
|
data_section: objects.StructType = next(
|
|
sec
|
|
for sec in dos_header.get_nt_header().get_sections()
|
|
if array_to_string(sec.Name) == ".data"
|
|
)
|
|
base: int = data_section.VirtualAddress + module_base
|
|
size: int = data_section.Misc.VirtualSize
|
|
|
|
# Looking at `Length` in TrueCrypt/Common/Password.h::Password struct
|
|
DWORD_SIZE_BYTES: int = 4
|
|
fmt = objects.DataFormatInfo(
|
|
length=DWORD_SIZE_BYTES, byteorder="little", signed=True
|
|
)
|
|
int32 = objects.templates.ObjectTemplate(
|
|
objects.Integer, pe_table_name + constants.BANG + "int", data_format=fmt
|
|
)
|
|
count, not_aligned = divmod(size, DWORD_SIZE_BYTES)
|
|
if not_aligned:
|
|
raise ValueError("PE data section not DWORD-aligned!")
|
|
|
|
lengths = self.context.object(
|
|
pe_table_name + constants.BANG + "array",
|
|
layer_name,
|
|
base,
|
|
count=count,
|
|
subtype=int32,
|
|
)
|
|
|
|
min_length = self.config.get("min-length")
|
|
for length in lengths:
|
|
|
|
if not min_length <= length <= 64:
|
|
continue
|
|
|
|
offset = length.vol["offset"] + DWORD_SIZE_BYTES
|
|
passphrase: objects.Bytes = self.context.object(
|
|
pe_table_name + constants.BANG + "bytes",
|
|
layer_name,
|
|
offset,
|
|
length=length,
|
|
)
|
|
|
|
|
|
if not all(0x20 <= c < 0x7F for c in passphrase):
|
|
continue
|
|
|
|
buf: objects.Bytes = self.context.object(
|
|
pe_table_name + constants.BANG + "bytes",
|
|
layer_name,
|
|
offset + length + 1,
|
|
length=3,
|
|
)
|
|
if any(buf):
|
|
continue
|
|
|
|
yield offset, passphrase.decode(encoding="ascii")
|
|
|
|
def _find_driver_bases(
|
|
self, mods: Iterable[interfaces.objects.ObjectInterface]
|
|
) -> List[int]:
|
|
driver_substr = (self.config.get("driver") or "").lower().strip()
|
|
|
|
def matches(mod_name: str, needle: str) -> bool:
|
|
return needle and needle in mod_name
|
|
|
|
def bases_for(needle: str) -> List[int]:
|
|
out: List[int] = []
|
|
for mod in mods:
|
|
try:
|
|
name = mod.BaseDllName.get_string().lower()
|
|
except Exception:
|
|
continue
|
|
if matches(name, needle):
|
|
out.append(int(mod.DllBase))
|
|
return out
|
|
|
|
if driver_substr:
|
|
bases = bases_for(driver_substr)
|
|
if bases:
|
|
return bases
|
|
|
|
for needle in ("veracrypt", "truecrypt"):
|
|
bases = bases_for(needle)
|
|
if bases:
|
|
return bases
|
|
|
|
return []
|
|
|
|
def _generator(self):
|
|
kernel = self.context.modules[self.config["kernel"]]
|
|
mods: Iterable[interfaces.objects.ObjectInterface] = modules.Modules.list_modules(
|
|
self.context, self.config["kernel"]
|
|
)
|
|
|
|
driver_bases = self._find_driver_bases(mods)
|
|
if not driver_bases:
|
|
vollog.warning(
|
|
"No VeraCrypt driver module found in the modules list. Unable to proceed."
|
|
)
|
|
return
|
|
|
|
seen = set()
|
|
for module_base in driver_bases:
|
|
try:
|
|
for offset, password in self.scan_module(module_base, kernel.layer_name):
|
|
key = (offset, password)
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
yield (0, (format_hints.Hex(offset), len(password), password))
|
|
except Exception as exc:
|
|
vollog.debug("Failed scanning module at 0x%x: %s", module_base, exc)
|
|
|
|
def run(self) -> renderers.TreeGrid:
|
|
return renderers.TreeGrid(
|
|
[
|
|
("Offset", format_hints.Hex),
|
|
("Length", int),
|
|
("Password", str),
|
|
],
|
|
self._generator(),
|
|
)
|
|
|