Init. commit
This commit is contained in:
101
HumanAI-Forensic-Hard/scripts/scan_vc_password_struct.py
Normal file
101
HumanAI-Forensic-Hard/scripts/scan_vc_password_struct.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import argparse
|
||||
import os
|
||||
import struct
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Iterator, Tuple
|
||||
|
||||
|
||||
def iter_input_files(paths: Iterable[str]) -> Iterator[Path]:
|
||||
for p in paths:
|
||||
path = Path(p)
|
||||
if path.is_dir():
|
||||
for child in sorted(path.rglob("*")):
|
||||
if child.is_file():
|
||||
yield child
|
||||
elif path.is_file():
|
||||
yield path
|
||||
|
||||
|
||||
def scan_password_structs(
|
||||
data: bytes, *, min_len: int, max_len: int
|
||||
) -> Iterator[Tuple[int, int, str]]:
|
||||
"""
|
||||
Heuristic scan for the (TrueCrypt/VeraCrypt) Password struct:
|
||||
uint32 Length; char Text[...];
|
||||
|
||||
We look for:
|
||||
<len:uint32le> <len bytes printable ASCII> <any byte> 0x00 0x00 0x00
|
||||
|
||||
This mirrors volatility3's truecrypt passphrase finder which validates the
|
||||
3 bytes *after* the presumed NUL terminator but doesn't explicitly check
|
||||
the terminator byte itself.
|
||||
"""
|
||||
mv = memoryview(data)
|
||||
n = len(data)
|
||||
min_total = 4 + min_len + 4
|
||||
if n < min_total:
|
||||
return
|
||||
|
||||
|
||||
unpack_from = struct.unpack_from
|
||||
for i in range(0, n - min_total + 1):
|
||||
(length,) = unpack_from("<I", mv, i)
|
||||
if length < min_len or length > max_len:
|
||||
continue
|
||||
|
||||
start = i + 4
|
||||
end = start + length
|
||||
tail = end + 4
|
||||
if tail > n:
|
||||
continue
|
||||
|
||||
pw = mv[start:end]
|
||||
|
||||
if any((c < 0x20 or c >= 0x7F) for c in pw):
|
||||
continue
|
||||
|
||||
if data[end + 1 : tail] != b"\x00\x00\x00":
|
||||
continue
|
||||
|
||||
try:
|
||||
pw_str = pw.tobytes().decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
yield i, length, pw_str
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("paths", nargs="+", help="Files and/or directories to scan")
|
||||
ap.add_argument("--min-len", type=int, default=5)
|
||||
ap.add_argument("--max-len", type=int, default=64)
|
||||
args = ap.parse_args()
|
||||
|
||||
seen = set()
|
||||
hits = 0
|
||||
for fp in iter_input_files(args.paths):
|
||||
try:
|
||||
data = fp.read_bytes()
|
||||
except Exception as exc:
|
||||
print(f"[!] Failed to read {fp}: {exc}")
|
||||
continue
|
||||
|
||||
for off, length, pw in scan_password_structs(
|
||||
data, min_len=args.min_len, max_len=args.max_len
|
||||
):
|
||||
key = (pw,)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
hits += 1
|
||||
print(f"{fp}\t0x{off:X}\t{length}\t{pw}")
|
||||
|
||||
if hits == 0:
|
||||
print("[!] No candidates found")
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user