Skip to content

Instantly share code, notes, and snippets.

@av-gantimurov
Last active April 16, 2026 13:43
Show Gist options
  • Select an option

  • Save av-gantimurov/803fd62ae422cedd0786792c4b9e6916 to your computer and use it in GitHub Desktop.

Select an option

Save av-gantimurov/803fd62ae422cedd0786792c4b9e6916 to your computer and use it in GitHub Desktop.
Kaspersky Quarantine unpacking tool
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Kaspersky Quarantine unpacking tool
Based on dexray.pl
Date: 2026-04-16
"""
import argparse
import json
import struct
from datetime import datetime
EPOCH_AS_FILETIME = 116444736000000000
SHIFT = 0x46120CE758A60000
def decode_attr(value):
if isinstance(value, bytes):
value = int.from_bytes(value, byteorder="little")
masks = {"I": 0x2000, "A": 0x0020, "S": 0x0004, "H": 0x0002, "R": 0x0001}
attr = []
for v, m in masks.items():
if value & m:
attr.append(v)
return "".join(attr)
def convert_dt(value: int):
if isinstance(value, bytes):
value = int.from_bytes(value, byteorder="little")
if value < SHIFT:
raise ValueError(f"Incorrect FILETIME {value}")
filetime = (value - SHIFT) / 10
ts = (filetime - EPOCH_AS_FILETIME) / 10000000
return datetime.fromtimestamp(ts)
def prepare_argparse() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("-v", "--verbose", help="Verbose output", action="store_true")
parser.add_argument(
"files",
help="File or directories to parse",
metavar="FILE",
nargs="+",
)
return parser
# def decode_property(data) -> tuple[str, bytes | str]:
def decode_property(data, is_verbose: bool = False):
idlen = struct.unpack("<I", data[0:4])[0]
if is_verbose:
print(f"Trying decode property: {data.hex()}")
if idlen < 5:
if is_verbose:
print("Property without meta")
idname = idlen
value = data[4:]
if idname == 1 or idname == 0:
value = value.decode("utf-16").rstrip("\x00")
else:
idname_raw = data[4 : 4 + idlen]
try:
idname = (idname_raw).decode().rstrip("\0")
except UnicodeDecodeError:
idname = idname_raw.hex()
value = bytes(data[4 + idlen :])
if idname == "cNP_QB_FULLNAME":
# vallen = length - idlen
value = value.decode("utf-16").rstrip("\x00")
elif idname.endswith("_TIME"):
value = str(convert_dt(value))
elif idname == "cNP_QB_FILE_ATTRIBUTES":
value = decode_attr(value)
return idname, value
def kav_unquarantine(file, is_verbose: bool = False):
with open(file, "rb") as quarfile:
data = bytearray(quarfile.read())
# check for KLQB header
magic = struct.unpack("<I", data[0:4])[0]
if magic != 0x42514C4B:
return None, None
fsize = len(data)
headerlen = struct.unpack("<I", data[8:12])[0]
metaoffset = struct.unpack("<I", data[0x10:0x14])[0]
metalen = struct.unpack("<I", data[0x20:0x24])[0]
origlen = struct.unpack("<I", data[0x30:0x34])[0]
if is_verbose:
print(f"Header len: {headerlen}")
print(f"MetaOffset: 0x{metaoffset:X}")
print(f"Meta length: {metalen}")
print(f"Orig length: {origlen}")
info = {}
if fsize < headerlen + origlen + metalen:
return None
if metaoffset < headerlen + origlen:
return None
key = [0xE2, 0x45, 0x48, 0xEC, 0x69, 0x0E, 0x5C, 0xAC]
curoffset = metaoffset
length = struct.unpack("<I", data[curoffset : curoffset + 4])[0]
while length:
if is_verbose:
print(f"Reading property: offset: 0x{curoffset:X}, length 0x{length:X}")
prop_data = data[curoffset + 4 : curoffset + 4 + length]
for i in range(length):
# data[curoffset + 4 + i] ^= key[i % len(key)]
prop_data[i] ^= key[i % len(key)]
idname, value = decode_property(prop_data)
info[idname] = curoffset - metaoffset, value
curoffset += 4 + length
if curoffset >= metaoffset + metalen:
break
length = struct.unpack("<I", data[curoffset : curoffset + 4])[0]
for i in range(origlen):
data[headerlen + i] ^= key[i % len(key)]
return (data[headerlen : headerlen + origlen], info)
def dump_info(info, fname, dump_all: bool = False):
if dump_all:
print("Dumping all meta to .bin")
else:
print("Dumping only binary to bin, others to meta")
txt = {}
for m in info:
off, val = info[m]
if isinstance(val, str):
txt[m] = val
if not dump_all:
continue
val = val.encode()
with open(f"{fname}.prop{off:04X}.bin", "wb") as w:
print(f"Store {m} to {w.name}")
if isinstance(m, str):
w.write(m.encode() + b"\x00")
w.write(val)
for m, val in txt.items():
print(f"{m}: {val}")
with open(f"{fname}.json", "w") as j:
json.dump(txt, j, indent=3, ensure_ascii=False)
print(f"Store meta in {j.name}")
def main() -> None:
parser = prepare_argparse()
args = parser.parse_args()
for f in args.files:
unp, info = kav_unquarantine(f, args.verbose)
if unp is None:
print(f"Unabled to unpack {f}")
continue
with open(f"{f}.unp", "wb") as fo:
print(f"Unpack quarantine to {fo.name}")
fo.write(unp)
dump_info(info, f"{f}.unp", args.verbose)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment