#!/usr/bin/env python3
# === StraightArc Technologies Open Toolset : banner & disclaimer =============
def _sta_accept():
    import sys, os
    line = "=" * 68
    print(line)
    print("               StraightArc Technologies Open Toolset")
    print(line)
    print('DISCLAIMER: This tool is provided on an "AS IS" basis. StraightArc')
    print("Technologies or any of its associates shall not be responsible for")
    print("the consequences of its use. Always operate on a COPY of the media,")
    print("never the original evidence.")
    print(line)
    if os.environ.get("STRAIGHTARC_ACCEPT") == "1":
        return
    if not sys.stdin.isatty():
        sys.exit("Non-interactive run: set STRAIGHTARC_ACCEPT=1 to proceed.")
    try:
        ans = input("Type 'accept' to acknowledge and continue: ").strip().lower()
    except (EOFError, KeyboardInterrupt):
        sys.exit("\nDisclaimer not accepted. Exiting.")
    if ans not in ("accept", "y", "yes"):
        sys.exit("Disclaimer not accepted. Exiting.")
_sta_accept()
# ============================================================================

"""Recover original files from UNENCRYPTED Quick Heal .cfa backups.
Format: 'QHBK' + 4 bytes + zlib(original file). The ransomware missed these -> full recovery."""
import json, struct, zlib, re, os, hashlib, sys
REC=1024;SEC=512;CLU=4096;DEV="/dev/sdf2"
OUT="/mnt/d/MS/quickheal_recovered"
TARGET=set(sys.argv[1].split(",")) if len(sys.argv)>1 else {"xls","xlsx"}
M={int(k):v for k,v in json.load(open("mft_meta.json")).items()}
def fixup(rec):
    o=struct.unpack_from("<H",rec,4)[0];n=struct.unpack_from("<H",rec,6)[0]
    if not o or not n:return rec
    rec=bytearray(rec)
    for i in range(1,n):
        p=i*SEC-2
        if p+2<=len(rec):rec[p:p+2]=rec[o+i*2:o+i*2+2]
    return bytes(rec)
def prl(data,off):
    runs=[];lcn=0;i=off
    while i<len(data):
        h=data[i]
        if h==0:break
        ls=h&0x0F;os_=(h>>4)&0x0F;i+=1
        if ls==0 or i+ls+os_>len(data):break
        rl=int.from_bytes(data[i:i+ls],"little");i+=ls
        if os_:
            ro=int.from_bytes(data[i:i+os_],"little",signed=True);i+=os_;lcn+=ro;runs.append((lcn,rl))
        else:runs.append((None,rl))
    return runs
fm=open("mft.raw","rb"); dev=open(DEV,"rb")
def extract(ino):
    fm.seek(ino*REC);rec=fixup(fm.read(REC))
    first=struct.unpack_from("<H",rec,20)[0];i=first;size=0;rl=None;res=None
    while i+8<=REC:
        at=struct.unpack_from("<I",rec,i)[0]
        if at==0xFFFFFFFF:break
        al=struct.unpack_from("<I",rec,i+4)[0]
        if al==0 or i+al>REC:break
        if at==0x80 and rec[i+9]==0:
            if rec[i+8]==0:
                cs=struct.unpack_from("<I",rec,i+16)[0];co=struct.unpack_from("<H",rec,i+20)[0];res=rec[i+co:i+co+cs];size=cs
            else:
                size=struct.unpack_from("<Q",rec,i+0x30)[0];rl=prl(rec[i:i+al],struct.unpack_from("<H",rec,i+0x20)[0])
        i+=al
    if res is not None:return res
    out=bytearray()
    for lcn,rln in (rl or []):
        if lcn is None:out+=b"\x00"*(rln*CLU)
        else:dev.seek(lcn*CLU);out+=dev.read(rln*CLU)
    return bytes(out[:size])
def orig_name(nm):
    b=nm[:-4] if nm.endswith(".cfa") else nm
    m=re.match(r'(.+)_(\d+)$', b)
    return (m.group(1), m.group(2)) if m else (b, "0")
def safe(s): return re.sub(r'[^\w .()\-\[\]]','_',s)[:120]

seen_hash=set()
counts={}; recovered=0; failed=0; dup=0
targets=[]
for ino,r in M.items():
    if r["d"] or not r["n"]: continue
    nm=r["n"]
    if not (nm.lower().endswith(".cfa") and not nm.endswith(".Funder")): continue
    base,bid=orig_name(nm)
    ext=os.path.splitext(base)[1].lower().lstrip(".")
    if ext not in TARGET: continue
    targets.append((ino,nm,base,bid,ext))
print(f"unencrypted .cfa matching {TARGET}: {len(targets)}", flush=True)
for k,(ino,nm,base,bid,ext) in enumerate(targets):
    try:
        d=extract(ino)
        z=d.find(b'\x78\x9c', 4)
        if z<0 or z>32: continue
        data=zlib.decompressobj().decompress(d[z:])
    except Exception:
        failed+=1; continue
    if len(data)<64: failed+=1; continue
    h=hashlib.sha256(data).digest()
    if h in seen_hash: dup+=1; continue
    seen_hash.add(h)
    od=os.path.join(OUT, ext); os.makedirs(od, exist_ok=True)
    fn=safe(base)
    dst=os.path.join(od, fn)
    if os.path.exists(dst):
        stem,e=os.path.splitext(fn); dst=os.path.join(od, f"{stem}__v{bid}{e}")
    try: open(dst,"wb").write(data); recovered+=1; counts[ext]=counts.get(ext,0)+1
    except Exception: failed+=1
    if k and k%1000==0: print(f"  ...{k}/{len(targets)} processed, {recovered} recovered", flush=True)
print(f"\nRECOVERED (unique originals): {recovered}")
print(f"duplicates skipped: {dup},  failed: {failed}")
print(f"by type: {counts}")
print(f"output: {OUT}")


# ============================================================================
# DISCLAIMER
# This script is provided on an "AS IS" basis. StraightArc Technologies or any
# of its associates shall not be responsible for the consequences of its use.
#
# A Contribution from StraightArc Technologies (info@straightarc.com)
# ============================================================================
