{
	"id": "9c71b3d7-6231-4a5b-b41c-4e3365c1de92",
	"created_at": "2026-04-06T01:32:15.64321Z",
	"updated_at": "2026-04-10T03:20:54.983937Z",
	"deleted_at": null,
	"sha1_hash": "4ec3feded662be7c8e7ae42418d437c951f09f66",
	"title": "Recovering some files encrypted by LockBit 2.0",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 178413,
	"plain_text": "Recovering some files encrypted by LockBit 2.0\r\nPublished: 2021-10-15 · Archived: 2026-04-06 00:44:01 UTC\r\nOct 15, 2021 · 1066 words · 6 minute read\r\nThe LockBit 2.0 ransomware has been incredibly “productive” these last few months: their technique is well\r\nautomated, and the list of compromised companies keeps growing every day.\r\nIn order to reduce the destructiveness of their payload, most ransomware operators do not encrypt every single file\r\non a system; instead, they set out a set of rules, for example:\r\nOnly encrypt files with specific extensions: .docx , .cpp , .db , .log\r\nDon’t encrypt files in C:\\Windows\\System32 , in order to keep a semi-working machine (otherwise how\r\nwould the users read the ransom note?)\r\nLockBit 2.0 has a pretty interesting quirk though: as an optimization, only the first 4KiB (4096 bytes) of each file\r\nare encrypted. This is usually enough to lock away important data and make file recovery a pain. It also speeds up\r\nthe encryption process.\r\nWe have also observed that the LockBit 2.0 ransomware is pretty generous in the extension list it encrypts: even\r\nuser hive files ( NTUSER.DAT ) are encrypted, which is a pain if we want to extract useful data from it. But registry\r\nhives can be pretty big, could we maybe recover some data anyway?\r\nRegistry hive structure 🔗\r\nIn order to understand how we can recover the hives, we must first have a look at how registry hives are stored on-disk. Willi Ballenthin’s python-registry has some good explanations, including a text file from a certain B.D.\r\nwhich goes over the structure of hives for both Windows 95 and NT. This document tells us that NT optimized\r\nhive loading by making the header the typical size of a page, 4KiB.\r\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 1 of 8\n\nThis means that LockBit only encrypts the header, and doesn’t touch the actual data of the hive. Is it possible to\r\nrestore the header? To answer this question, we must list everything the header contains:\r\nA magic number ( regf )\r\nSequence numbers (used for inconsistency detection)\r\nModification timestamp\r\nVersion numbers\r\nHive name\r\nHive flags\r\nHeader checksum\r\nBasically, we can see that the header is mostly self-contained: there’s no reference to hbin offset, or a global\r\nhive checksum. There should be no problem restoring the header by copying it from another hive of the same type\r\n😊\r\nRestoring the hive 🔗\r\nSimply by copying over the first 4096 bytes from another, clear NTUSER.DAT , we were able to entirely recover all\r\nour user hives!\r\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 2 of 8\n\n$ regrip.py --ntuser ./ntuser_recovered.dat userassist | wc -l\r\n84\r\nIt works! RegRippy will be confused when trying to give you the user names when extracting data from\r\nNTUSER.DAT , because it guesses them based on the hive name, which has been copied over from a clean hive.\r\nOther than that, everything works as expected, and all data is accessible.\r\nIf you ever encounter this issue, here’s a script which can restore an encrypted NTUSER.DAT hive: it’s basically\r\nrebuilding the header and replacing it to create a clean hive.\r\n#!/usr/bin/env python3\r\nimport argparse\r\ndef main():\r\n parser = argparse.ArgumentParser(description=\"Fix encrypted hives by repairing the header (only for NTUSER.D\r\n parser.add_argument(\"--user\", type=str, help=\"The user name to store in the header (default: JohnDoe)\", defa\r\n parser.add_argument(\"hive_path\", type=str, help=\"Encrypted hive path\")\r\n parser.add_argument(\"output\", type=str, help=\"Where to output the fixed hive\")\r\n args = parser.parse_args()\r\n hive_name = \"??\\\\C:\\\\Users\\\\\" + args.user + \"\\\\ntuser.dat\"\r\n encoded_hive_name = hive_name.encode(\"utf-16-le\")\r\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 3 of 8\n\nif len(encoded_hive_name) \u003e 64:\r\n encoded_hive_name = encoded_hive_name[:64]\r\n else:\r\n encoded_hive_name += b\"\\x00\" * (64 - len(encoded_hive_name))\r\n header = b\"regfH\\x1E\\x00\\x00\\x48\\x1E\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x05\\x00\\x00\\x00\r\n header += encoded_hive_name\r\n header += b\"\\x43\\xBE\\x11\\x44\\xFF\\x07\\xE8\\x11\\x92\\x75\\xEA\\x28\\xA0\\xD0\\x3E\\x60\\x43\\xBE\\x11\\x44\\xFF\\x07\\xE8\\x11\r\n header += b\"\\x00\"*316\r\n header += b\"\\xD8\\xC9\\x70\\x75\"\r\n header += b\"\\x00\"*3584\r\n with open(args.hive_path, \"rb\") as h1:\r\n with open(args.output, \"wb\") as h2:\r\n data = h1.read()\r\n without_header = data[4096:]\r\n h2.write(header)\r\n h2.write(without_header)\r\n print(\"Done! Hive written to\", args.output)\r\nif __name__ == \"__main__\":\r\n main()\r\n(Update) Extending the technique to other file types 🔗\r\nIn passing, @citronneur mentioned that EVTX files also had a 4 KiB header. Maybe they could be reconstructed\r\nas well?\r\nWhen investigating Windows file formats, it’s always a good idea to check Joachim Metz’s libyal repositories.\r\nIn that case, bingo! libevtx exists, with some very detailed documentation.\r\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 4 of 8\n\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 5 of 8\n\nBasically, an EVTX file is composed of several chunks, and each chunk contains a number of records. Each\r\nrecord has an ID, which is unique across all chunks.\r\nWe assume the first chunk number is always 0. To get the last chunk number, we will search for the signature\r\n\"ElfChnk\\x00\" and count its occurrences. We assume chunks are numbered in increasing order, starting from\r\nzero.\r\nTo get the last record ID, we first get the last chunk (easy, because each chunk has a fixed size), and parse the\r\noffset to the last record from its header. We then parse the record at this offset to extract its ID.\r\nThe checksum is a simple CRC32 of the first 120 bytes of the file header. With this, we are able to recreate all the\r\ndata from the encrypted file header and read the events!\r\nAnd here is a Python script which does just that:\r\n#!/usr/bin/env python3\r\nimport argparse\r\nimport binascii\r\nimport sys\r\ndef get_number_of_chunks(data):\r\n count = 0\r\n needle = b\"ElfChnk\\x00\"\r\n for offset in range(len(data) - len(needle)):\r\n if data[offset : offset + len(needle)] == needle:\r\n count += 1\r\n return count\r\ndef get_chunk(data, n):\r\n data = data[4096:] # get rid of header\r\n print(\"[+] Getting chunk\", n)\r\n chunk = data[n * 65536 : (n + 1) * 65536]\r\n assert chunk[:8] == b\"ElfChnk\\x00\"\r\n return chunk\r\ndef get_last_record(chunk):\r\n offset = int.from_bytes(chunk[44:48], byteorder=\"little\")\r\n print(f\"[+] Last record offset: {offset} (0x{offset:x})\")\r\n if offset == 0:\r\n print(\"[!] Error: this EVTX file probably has no events\")\r\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 6 of 8\n\nsys.exit(1)\r\n record = chunk[offset:]\r\n assert record[:4] == b\"\\x2a\\x2a\\x00\\x00\"\r\n return record\r\ndef get_record_id(record):\r\n i = int.from_bytes(record[8:16], byteorder=\"little\")\r\n print(\"[+] Record id:\", i)\r\n return i\r\ndef main():\r\n parser = argparse.ArgumentParser(description=\"Fix LockBit2.0 EVTX file\")\r\n parser.add_argument(\"file\", type=str, help=\"Path to evtx file\")\r\n parser.add_argument(\"output\", type=str, help=\"Where to store the resulting file\")\r\n args = parser.parse_args()\r\n data = None\r\n with open(args.file, \"rb\") as f:\r\n data = f.read()\r\n print(\"[+] Loaded\", args.file)\r\n chunks = get_number_of_chunks(data)\r\n print(\"[+] Number of chunks:\", chunks)\r\n signature = b\"ElfFile\\x00\"\r\n first_chunk_number = (0).to_bytes(8, byteorder=\"little\")\r\n last_chunk_number = (chunks - 1).to_bytes(8, byteorder=\"little\")\r\n next_record_id = get_record_id(get_last_record(get_chunk(data, chunks - 1))) + 1\r\n next_record_id = next_record_id.to_bytes(8, byteorder=\"little\")\r\n header_size = (128).to_bytes(4, byteorder=\"little\")\r\n minor_version = (1).to_bytes(2, byteorder=\"little\")\r\n major_version = (3).to_bytes(2, byteorder=\"little\")\r\n header_block_size = (4096).to_bytes(2, byteorder=\"little\")\r\n number_of_chunks = chunks.to_bytes(2, byteorder=\"little\")\r\n unk1 = b\"\\x00\" * 76\r\n file_flags = (0).to_bytes(4, byteorder=\"little\")\r\n crc32 = -1\r\n unk2 = b\"\\x00\" * 3968\r\n header = (\r\n signature\r\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 7 of 8\n\n+ first_chunk_number\r\n + last_chunk_number\r\n + next_record_id\r\n + header_size\r\n + minor_version\r\n + major_version\r\n + header_block_size\r\n + number_of_chunks\r\n + unk1\r\n + file_flags\r\n )\r\n crc32 = binascii.crc32(header[:120]) \u0026 0xFFFFFFFF\r\n header += crc32.to_bytes(4, byteorder=\"little\")\r\n header += unk2\r\n assert (len(header)) == 4096\r\n with open(args.output, \"wb\") as f:\r\n f.write(header)\r\n f.write(data[4096:])\r\nif __name__ == \"__main__\":\r\n main()\r\nSource: https://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nhttps://skyblue.team/posts/hive-recovery-from-lockbit-2.0/\r\nPage 8 of 8",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://skyblue.team/posts/hive-recovery-from-lockbit-2.0/"
	],
	"report_names": [
		"hive-recovery-from-lockbit-2.0"
	],
	"threat_actors": [],
	"ts_created_at": 1775439135,
	"ts_updated_at": 1775791254,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/4ec3feded662be7c8e7ae42418d437c951f09f66.pdf",
		"text": "https://archive.orkl.eu/4ec3feded662be7c8e7ae42418d437c951f09f66.txt",
		"img": "https://archive.orkl.eu/4ec3feded662be7c8e7ae42418d437c951f09f66.jpg"
	}
}