Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for decrypting System DPAPI secrets #305

Merged
merged 15 commits into from
Sep 20, 2023
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ tests/data/plugins/browsers/firefox/places.sqlite filter=lfs diff=lfs merge=lfs
tests/data/plugins/browsers/chrome/History.sqlite filter=lfs diff=lfs merge=lfs -text
tests/data/plugins/browsers/edge/History.sqlite filter=lfs diff=lfs merge=lfs -text
tests/data/plugins/browsers/chromium/History.sqlite filter=lfs diff=lfs merge=lfs -text
tests/data/dpapi/** filter=lfs diff=lfs merge=lfs -text
43 changes: 27 additions & 16 deletions dissect/target/plugins/apps/vpns/wireguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from os.path import basename
from typing import Iterator, Union

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugin import Plugin, export
from dissect.target.plugin import Plugin, export, OperatingSystem

WireGuardInterfaceRecord = TargetRecordDescriptor(
"application/vpn/wireguard/interface",
Expand Down Expand Up @@ -62,33 +63,43 @@ class WireGuardPlugin(Plugin):
- C:\\Program Files\\WireGuard\\Data\\Configurations
"""

config_globs = [
# Linux
"/etc/wireguard/*.conf",
# MacOS
"/usr/local/etc/wireguard/*.conf",
"/opt/homebrew/etc/wireguard/*.conf",
]
os_config_globs = {
OperatingSystem.UNIX.value: ["/etc/wireguard/*.conf"],
OperatingSystem.LINUX.value: ["/etc/wireguard/*.conf"],
OperatingSystem.OSX.value: ["/usr/local/etc/wireguard/*.conf", "/opt/homebrew/etc/wireguard/*.conf"],
OperatingSystem.WINDOWS.value: [
r"C:\Windows\System32\config\systemprofile\AppData\Local\WireGuard\Configurations\*.dpapi",
r"C:\Program Files\WireGuard\Data\Configurations\*.dpapi",
],
}

def __init__(self, target) -> None:
super().__init__(target)
self.configs = []
for path in self.config_globs:
cfgs = list(self.target.fs.path().glob(path.lstrip("/")))
if len(cfgs) > 0:
for cfg in cfgs:
self.configs.append(cfg)
config_globs = self.os_config_globs.get(target.os, [])
for path in config_globs:
self.configs.extend(self.target.fs.path().glob(path.lstrip("/")))
pyrco marked this conversation as resolved.
Show resolved Hide resolved

def check_compatible(self) -> bool:
if len(self.configs) > 0:
return True
if not self.configs:
raise UnsupportedPluginError("No Wireguard configuration files found")
return True

@export(record=[WireGuardInterfaceRecord, WireGuardPeerRecord])
def config(self) -> Iterator[Union[WireGuardInterfaceRecord, WireGuardPeerRecord]]:
"""Parses interface config files from wireguard installations."""

for config_path in self.configs:
config = _parse_config(config_path.read_text())
config = config_path.read_bytes()
if self.target.os == OperatingSystem.WINDOWS.value:
blob = self.target.dpapi.decrypt_dpapi_system_blob(config)
if blob:
config = blob.cleartext
else:
continue
config = config.decode()
print(config)
config = _parse_config(config)

for section in config.sections():
if "Interface" in section:
Expand Down
Empty file.
101 changes: 101 additions & 0 deletions dissect/target/plugins/os/windows/dpapi/blob.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from dissect.target.plugins.os.windows.dpapi.types.dpapi import DPAPIBlobStruct
from dissect.target.plugins.os.windows.dpapi import crypto


class DPAPIBlob:
cobyge marked this conversation as resolved.
Show resolved Hide resolved
"""Represents a DPAPI blob"""

def __init__(self, data: bytes):
self._blob = DPAPIBlobStruct.read(data)

self.version = self._blob.dwVersion
self.provider = self._blob.provider
self.mkguid = self._blob.guid
self.mkversion = self._blob.mkVersion
self.flags = self._blob.flags
self.description = self._blob.description.decode("UTF-16LE").encode("utf-8")
cobyge marked this conversation as resolved.
Show resolved Hide resolved
self.cipherAlgo = crypto.CryptoAlgo(self._blob.CipherAlgId)
self.keyLen = self._blob.keyLen
self.hmac = self._blob.hmac
self.strong = self._blob.strong
self.hashAlgo = crypto.CryptoAlgo(self._blob.CryptAlgId)
self.hashLen = self._blob.hashLen
self.cipherText = self._blob.cipherText
self.salt = self._blob.salt
self.blob = self._blob.blob
self.sign = self._blob.sign

self.cleartext = None
self.decrypted = False
self.signComputed = None

def decrypt(self, masterkey, entropy=None, strongPassword=None, smartCardSecret=None):
Schamper marked this conversation as resolved.
Show resolved Hide resolved
"""Try to decrypt the blob. Returns True/False
:rtype : bool
cobyge marked this conversation as resolved.
Show resolved Hide resolved
:param masterkey: decrypted masterkey value
:param entropy: optional entropy for decrypting the blob
:param strongPassword: optional password for decrypting the blob
:param smartCardSecret: MS Next Gen Crypto secret (e.g. from PIN code)
"""
for algo in [crypto.CryptSessionKeyType1, crypto.CryptSessionKeyType2]:
sessionkey = algo(
masterkey,
self.salt,
self.hashAlgo,
entropy=entropy,
smartcardsecret=smartCardSecret,
strongPassword=strongPassword,
)
key = crypto.CryptDeriveKey(sessionkey, self.cipherAlgo, self.hashAlgo)
cipher = self.cipherAlgo.module.new(
key[: int(self.cipherAlgo.keyLength)],
mode=self.cipherAlgo.module.MODE_CBC,
IV=b"\x00" * int(self.cipherAlgo.ivLength),
)
self.cleartext = cipher.decrypt(self.cipherText)
padding = self.cleartext[-1]
if padding <= self.cipherAlgo.blockSize:
self.cleartext = self.cleartext[:-padding]
# check against provided HMAC
self.signComputed = algo(
masterkey,
self.hmac,
self.hashAlgo,
entropy=entropy,
smartcardsecret=smartCardSecret,
verifBlob=self.blob,
)
self.decrypted = self.signComputed == self.sign
if self.decrypted:
return True
self.decrypted = False
return self.decrypted

def __repr__(self):
s = [
"DPAPI BLOB",
"\n".join(
(
"\tversion = %(version)d",
"\tprovider = %(provider)s",
"\tmkey = %(mkguid)s",
"\tflags = %(flags)#x",
"\tdescr = %(description)s",
"\tcipherAlgo = %(cipherAlgo)r",
"\thashAlgo = %(hashAlgo)r",
)
)
% self.__dict__,
"\tsalt = %s" % self.salt.hex(),
"\thmac = %s" % self.hmac.hex(),
"\tcipher = %s" % self.cipherText.hex(),
"\tsign = %s" % self.sign.hex(),
]
if self.signComputed is not None:
s.append("\tsignComputed = %s" % self.signComputed.hex())
if self.cleartext is not None:
s.append("\tcleartext = %r" % self.cleartext)
return "\n".join(s)


# vim:ts=4:expandtab:sw=4
cobyge marked this conversation as resolved.
Show resolved Hide resolved
Loading