{
	"id": "05556333-87e1-4f0d-9e97-c04f9aa9c36b",
	"created_at": "2026-04-06T00:17:47.492937Z",
	"updated_at": "2026-04-10T03:20:29.989694Z",
	"deleted_at": null,
	"sha1_hash": "5e6f1b1978b9421e4a98d3ccecb9a777d5bec829",
	"title": "Dissecting LockBit v3 ransomware",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 5783325,
	"plain_text": "Dissecting LockBit v3 ransomware\r\nBy Nhân Huỳnh, Hoang Nguyen, Thai Duong\r\nPublished: 2026-03-30 · Archived: 2026-04-05 13:25:33 UTC\r\nIn our last article, we recommended analyzing ransomware binaries as part of an effective ransomware response\r\nstrategy:\r\n“Analyzing binaries is hard. Analyzing obfuscated ransomware is even harder.\r\n[...] However, it is worth investing in analyzing and understanding ransomware. Crypto breaking bugs\r\nmay be rare, but they are not impossible to find. In addition, ransomware authors may not fully\r\nunderstand how to use crypto correctly. The only way to determine if it is possible to recover the data, if\r\nany, is the long and detailed ransomware analysis by an expert team.\r\n[...] In addition, a successful analysis can help reassure you that there are no potential bugs in the\r\nencryption and decryption process. It also helps the technical team understand and potentially improve\r\nthe recovery process. This is an investment that should be considered early on in an incident.”\r\nIn this article, we show some examples of crucial intelligence you can gain from a meticulous and accurate\r\nransomware analysis. The target of this analysis is a variant of the LockBit v3 ransomware that we encountered in\r\na recent engagement. This variant is also known as LockBit Black due to some code similarity with the\r\nBlackMatter family. These samples are built from the leaked LockBit v3 builder available on GitHub.\r\nCalif discovered two issues in this version of the ransomware:\r\na crypto bug that may allow for the decryption of a portion of the data without the private key, i.e., without\r\npaying the ransom.\r\na design flaw that may cause data corruption and permanent data loss.\r\nWe decided to publish this analysis for the following reasons:\r\nThe crypto bug is already known to the malware author. We have observed newer variants where we can no\r\nlonger take advantage of this bug.\r\nWe want to share our analysis and research to help other affected organizations prepare and respond to the\r\nsame ransomware family, especially regarding the data corruption flaw.\r\nThe LockBit v3 family contains interesting anti-analysis techniques and clever use of standard\r\ncryptographic algorithms that are not well documented. These technical details would be valuable for\r\nmalware researchers and threat hunters.\r\nWe also publish an open-source decryptor for this variant. You can download the tool from GitHub.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 1 of 21\n\nCalif would like to extend a special thank you to Chuong Dong – a malware expert who has previous experience\r\nwith this ransomware family. During the initial analysis, we requested Chuong’s assistance to swiftly comprehend\r\nthe file encryption scheme. His help proved highly valuable as we managed to quickly reimplement the decryptor.\r\nNote that the screenshots and code snippets within this article assume that the encryptor is loaded at address\r\n0xFA0000 instead of the default ImageBase of 0x400000. The decryptor is loaded at the ImageBase of 0x400000.\r\nIn addition, the ransomware has many anti-debugging and obfuscation mechanisms. To bypass these protections\r\nand reproduce this analysis, please refer to Appendix A: Reverse engineering detail.\r\nIntroduction\r\nEncryption and decryption logic\r\nEncrypted file structure\r\nFooter structure\r\nModified Salsa20\r\nRSA with no padding\r\nFile encryption\r\nFile decryption\r\nFlaws\r\nKeystream reuse vulnerability\r\nData corruption\r\nConclusion\r\nAppendix A: Reverse engineering detail\r\nAppendix B: Open-source decryption tool\r\nAppendix C: Binary Information and Indicators of Compromise (IOCs)\r\nAppendix D: IDC script to rename functions\r\nAppendix E: Chunk counts and skip bytes\r\nThe sample encrypts files using a combination of symmetric and asymmetric cryptography, as follows:\r\nGenerate a 64-byte random key for each targeted file. We will refer to it as the file_encryption_key.\r\nWe identify the encryption algorithm as a variant of Salsa20. Normally, Salsa20 uses a 32-byte key, but\r\nthis variant uses 64-byte. Please refer to the Modified Salsa20 section for more details. Unless specified\r\notherwise, all references to Salsa20 in this document refer to this modified version.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 2 of 21\n\nGenerate another 64-byte random Salsa20 key to encrypt the file_encryption_key. We will refer to this\r\nsecond key as the key_encryption_key. As an optimization to reduce the number of slow RSA encryption\r\noperations, the sample reuses this key for 1,000 files before generating a new one. This key reuse leads to a\r\nvulnerability described in the Keystream reuse vulnerability section.\r\nEncrypt the key_encryption_keys using RSA with no padding, using a 1024-bit public key embedded\r\nwithin. We describe this algorithm in the RSA with no padding section. Note that since 2015 NIST has\r\nrecommended against using 1024-bit RSA keys.\r\nThe sample processes targeted files the same way during encryption and decryption. It divides each file into\r\nchunks of 0x20000 bytes. The sample does not pad the file if the file size or the size of the last chunk is less than\r\n0x20000 bytes.\r\nConsecutive chunks form a group. There are three group types: before, skip, and after group. There is exactly\r\none “before group” at the beginning of the file. The skip group and the after group follow the before group and\r\nrepeat alternatively throughout the rest of the file.\r\nThe sample encrypts chunks of the before group and after groups using Salsa20. It leaves chunks in the skip group\r\nunencrypted. It determines the number of chunks in each group based on the file size. Please refer to Appendix E\r\nfor more details.\r\nAn encrypted file ends with a footer containing information about the file such as the file’s original name, number\r\nof chunks in each group, etc, including the file_encryption_key to decrypt the file data. The sample encrypts this\r\nfooter, and appends it to the file after the encryption finishes. For a detailed description of the footer structure,\r\nrefer to the next section.\r\nThe overall structure of an encrypted file can be visualized as follows:\r\nWe reconstruct the overall structure of the footer in the C snippet below:\r\nstruct file_encryption_info\r\n{\r\n char filename[file_encryption_info.filename_size]; // apLib compressed\r\n uint16_t filename_size;\r\n LARGE_INTEGER skipped_bytes;\r\n int before_chunk_count;\r\n int after_chunk_count;\r\n uint8_t file_encryption_key[0x40];\r\n};\r\nstruct key_encryption_info\r\n{\r\n uint16_t file_encryption_info_length; // necessary because filename is dynamically sized\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 3 of 21\n\nint checksum;\r\n union\r\n {\r\n struct\r\n {\r\n uint8_t key_encryption_key[0x40];\r\n uint8_t checksum[0x40];\r\n } decrypted;\r\n uint8_t encrypted_key_encryption_key[0x80]; // RSA encrypted\r\n } key_blob;\r\n};\r\nstruct footer\r\n{\r\n struct file_encryption_info file_encryption_info; // Salsa20 encrypted\r\n struct key_encryption_info key_encryption_info;\r\n};\r\nThe file_encryption_info contains a randomly generated key to decrypt the file content. The file_encryption_info\r\nis encrypted using Salsa20. The key to decrypt the file_encryption_info is stored in the\r\nencrypted_key_encryption_key field of the key_encryption_info structure. This field, in turn, is encrypted using\r\nthe RSA public key embedded in the ransomware.\r\nThe decryptor contains an embedded private key, and works as follows:\r\n1. Read the key_encryption_info structure at offset 0x86 bytes from the end of the file.\r\n2. Hash the encrypted_key_encryption_key field and verify it against the checksum field as seen here.\r\n3. Decrypt the encrypted_key_encryption_key using the embedded private RSA key then validate\r\nthe key_encryption_key with the decrypted.checksum field.\r\n4. Calculate the start of the file_encryption_info structure using the file_encryption_info_length field.\r\n5. Use the key_encryption_key to decrypt the file_encryption_info structure using the modified Salsa20\r\nalgorithm. This structure contains the Salsa20 file_encryption_key that can be used to decrypt the chunks\r\nin the before group and after group.\r\nThe sample encrypts the file_encryption_info structure and the chunks using Salsa20 at address 0x00FA20AC.\r\nSalsa20 has a 64-byte state that is used to generate a key stream to encrypt the plaintext one 64-byte block at a\r\ntime. In the vanilla Salsa20 standard, the initial 64-byte state consists of a 32-byte key, an 8-byte block counter, an\r\n8-byte nonce, and a 16-byte constant that spell “expand 32-byte k” in ASCII.\r\nHowever, in this variant, the entire initial state is filled with random values. The aforementioned\r\nfile_encryption_key and key_encryption_key are the initial states of the file encryption and key encryption\r\nprocesses respectively.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 4 of 21\n\nThis finding shows that LockBit v3 is indeed a successor of BlackMatter, which in turn came from the Darkside\r\nransomware family. Chuong’s analysis of Darkside shows that it also fills the Salsa20’s initial state, which\r\nChuong called the matrix, with random values.\r\nThis sample encrypts key_encryption_info.key_encryption_key and key_encryption_info.checksum, using a\r\ncustom implementation of the RSA algorithm at address 0x00FA17B4.\r\nRecall that an RSA public key consists of two components:\r\nThe modulus N.\r\nThe public exponent e.\r\nTo encrypt a message m using RSA with no padding, you compute m^e (mod N). This encryption mode, which is\r\nknown as textbook RSA, has many potential footguns. For example, it’s possible to recover small messages.\r\nTherefore, m is usually padded with PKCS v1.5 or OAEP padding schemes.\r\nHowever, the sample uses no padding. We can’t find any obvious issues, because the sample only encrypts\r\nmessages that have the same size as the modulus. In particular, it uses a 1024-bit key to encrypt\r\nkey_encryption_info.key_encryption_key and key_encryption_info.checksum, which in total are also 1024 bits\r\nlong.\r\nBefore encrypting any files, the sample parses its embedded configuration at address  0x00FC600C. This data are\r\nencrypted by the function at 0x00FA6F48 and contain information such as configuration flags, file hashes to\r\navoid, ransom note, and the RSA public key used to encrypt the randomly generated\r\nkey_encryption_info.key_encryption_key.\r\nAfter decrypting its configurations, the sample parses its command line arguments and enumerates target paths to\r\nencrypt files. The sample operates slightly differently depending on the command line argument. However, the file\r\nencryption logic is similar across different execution flows. The sample creates one thread for traversing and\r\nqueueing files to be encrypted and multiple threads to actually encrypt the files. The threads communicate\r\nasynchronously with each other using an IO completion port.\r\nAt a high-level, the encryption threads work as follows:\r\nThe file traversal and queueing logic starts at 0x00FAF308.\r\nIt drops a ransom note in the current directory.\r\nFor each file in the current target directory, it verifies the filename against the lists of hashes to avoid. If the\r\ncurrent filename doesn’t belong to any of the lists, it renames the current file and adds a unique extension.\r\nIn our variant, the extension is .IzYqBW5pa.\r\nIt increases various counters, including a counter for the number of files using the current\r\nkey_encryption_key. This key is randomly generated and reused once every 1,000 files. Once this counter\r\nreaches 1,000, the sample resets it back to 0 and generates a new key_encryption_key. This design\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 5 of 21\n\nintroduces a bug that allows for the decryption without paying the ransom. This bug is described in detail\r\nin the Key stream reuse vulnerability section.\r\nIt fills out and sets up the key_encryption_info structure for the current file. The logic to set the\r\nbefore_chunk_count, skipped_bytes, and after_chunk_count is at address 0x00FAE8AC. These values are\r\ndetermined based on the current file size. Refer to Appendix E for the exact values of each field based on\r\nthe current file size.\r\nThe file encryption thread logic starts at address 0x00FADE78. This function simply determines if it needs\r\nto encrypt the current chunk depending on the file_encryption_info structure. It uses a randomly generated\r\nkey stored at file_encryption_info.file_encryption_key to encrypt each chunk using Salsa20. Finally, when\r\nthe entire file is processed, it writes the footer structure to the end of the file.\r\nThe decryptor binary LB3Decryptor.exe is not obfuscated and can be quickly analyzed statically.\r\nSimilar to the encryptor, the decryptor parses its command line arguments and enumerates paths to decrypt files.\r\nThe sample also creates multiple threads for decrypting and one for traversing and queueing files. These threads\r\ncommunicate asynchronously with each other using an IO completion port.\r\nAt a high-level, the decryption threads work as follows:\r\nThe file traversal and queueing logic starts at 0x00403CEC. For each file, it decrypts the\r\nkey_encryption_info (see Footer structure) at address 0x00403960. Then, it obtains\r\nthe file_encryption_key and the chunk counts before queueing the file.\r\nThe file decryption thread logic starts at 0x004030DC. It decrypts the chunks selected by the grouping\r\nalgorithm using the file_encryption_key. Finally, when the entire file is processed, it removes the encrypted\r\nfooter structure at the end of the file.\r\nThis version of the LockBit v3 ransomware has a keystream reuse vulnerability.\r\nInstead of directly encrypting the file_encryption_info structure with RSA, the sample aims to reduce the number\r\nof slow RSA operations by adding another layer of Salsa20 encryption. This is where it makes a mistake that may\r\nallow the recovery of a portion of the data.\r\nThe sample generates a random Salsa20 key_encryption_key  to encrypt the file_encryption_info structure once\r\nevery 1,000 files as seen below:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 6 of 21\n\nTherefore, the Salsa20 algorithm would generate the same key stream for 1,000 files from the same key. Within\r\nthese 1,000 files, if there is a file with a sufficiently long compressed filename, we can recover enough of the\r\nkeystream to decrypt the file_encryption_info structure of other files with a much shorter compressed filename.\r\nThis file_encryption_info structure contains the file_encryption_key to decrypt the file content. In other words, if\r\nwe happen to have a file with a sufficiently long compressed filename, chances are we can recover the content of\r\nother files with shorter compressed filenames without the private key from the threat actor, i.e., without paying the\r\nransom.\r\nFor example, we created two short text files for our test case:\r\na.txt, whose compressed filename is:  61 e0 2e e0 74 e0 78 db 09 02 00 00\r\naABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}~123456789.txt, whose\r\ncompressed filename is shown below:\r\n   The content of the encrypted file with the longer file name is shown here:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 7 of 21\n\nBecause we know the current filename, we can compute the plaintext compressed filename. XOR-ing the\r\nencrypted compressed filename with the plaintext compressed filename gives us the following keystream:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 8 of 21\n\nThe content of the encrypted a.txt is shown below with similar color-coded fields:\r\nXOR-ing the entire file_encryption_info block, starting at offset  0x0e to offset 0x6c with the keystream above\r\nwould give us the following bytes:\r\nThe recovered file_encryption_info fields are:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 9 of 21\n\nCompressed filename: 61 e0 2e e0 74 e0 78 db 09 02 00 00 (compressed a.txt)\r\nfilename_size: 0c 00 (0x0c)\r\nskipped_bytes: 00 00 52 00 00 00 00 00 (0x520000 in little-endian)\r\nbefore_chunk_count: 03 00 00 00 (0x03 in little-endian)\r\nafter_chunk_count: 03 00 00 00 (0x03 in little-endian)\r\nfile_encryption_key:\r\nThe recovered file_encryption_info structure allows us to decrypt the entire file following the decryption scheme\r\ndescribed above.\r\nThis version of the LockBit v3 ransomware has a design flaw that can cause permanent data loss. LockBit v3 has\r\na mutex checking mechanism to ensure only one instance of itself is running on the infected system:\r\nHowever, this feature can be configured at build time and is disabled in our sample. The flag at byte_FC5129 is\r\npart of the sample’s encrypted settings configured by the TA and set by the builder. When this feature is disabled,\r\nmultiple instances of the ransomware can run on the infected system at one time.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 10 of 21\n\nThe sample needs to process each file with exclusive access to the encryption logic. To do that, it attempts to\r\nterminate other processes that prevent exclusive access to the file. The sample uses the restart manager family of\r\nAPIs (RmStartSession(), RmRegisterResource(), RmGetList()) to get a list of processes with open handles to the\r\nfile being encrypted. It then terminates all of those processes.\r\nThis design can cause permanent data corruption because of the following reasons:\r\nWith multiple instances of the same ransomware running on the same system, one instance can attempt to\r\nterminate the other instance that is encrypting the same file. In this case, the randomly generated\r\nfile_encryption_key from the 1st instance can not be recovered. The file is permanently corrupted. We can\r\ndetect the corruption by observing files with multiple extra extensions, signaling that the files were\r\nencrypted multiple times. Each instance of the ransomware can have multiple encryption threads running\r\nparallel, each of which encrypts one file at a time. Since the number of concurrent threads is quite low, the\r\nnumber of files being affected in this case can potentially be low.\r\nThe sample may attempt to terminate another process that is currently writing and modifying the current\r\nfile. This may cause data corruption depending on how the affected process is designed. We can not easily\r\ndetect this case. However, the number of files being affected can be very high depending on the services\r\nrunning on the infected system and their utilization. Calif has observed files that are properly decrypted but\r\nare corrupted and not recognized by their associated applications.\r\nAnalyzing the ransomware could provide critical intelligence when evaluating response strategies to ransomware\r\nattacks. In this case, Calif observed flaws in the ransomware design that allowed affected organizations to\r\nreconsider the true value of the ransom demand. We hope our analysis helps demystify the inner workings of one\r\nransomware variant. We also hope to encourage more sharing of technical analysis, curated intelligence, and\r\nvaluable lessons across organizations. Security demands collaboration as no organizations operate in a vacuum.\r\nThe more secure our peers, the safer we are against cyber criminals.\r\nTypically, malware does not want to be analyzed. With a debugger, we can easily control the malware’s execution,\r\ndump data, or force the malware to execute a specific code path. Therefore, malware usually contains multiple\r\nanti-debugging checks. We found multiple said checks in this sample.\r\nThe first check occurs at 0x00FA63C5 (offset 0x57C5 into the file) as seen below:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 11 of 21\n\nAfter manually resolving some Windows Application Programming Interfaces (APIs), the sample calls the\r\nRtlCreateHeap() function to create a new heap. The result is a HANDLE to a window HEAP structure provided by\r\nthe operating system for the current process. This HEAP structure is undocumented by Microsoft. To better\r\nunderstand this structure, refer to other online resources regarding the Windows HEAP. Significant to anti-debugging mechanisms, the HEAP structure contains two flags: Flag and ForceFlag. These values change\r\ndepending on whether the current process is running under a debugger.\r\nIn the screenshot above, the sample checks the Flag field, which is at offset 0x40 byte into the undocumented\r\nHEAP structure. The value of this Flag field is 0x40041062 as shown in the screenshot below:\r\nThe sample gets the most significant 4 bits of the flag by rotating the Flag field 28 (0x1c) bits to the right, and\r\ntests the result against 0x04. This effectively tests the most significant byte of the Flag field against 0x40000000\r\n(HEAP_VALIDATE_PARAMETER_ENABLED) which is set if the current process is running under a debugger.\r\nIf the sample detects a debugger, it modifies the HANDLE to the current process’s heap using the rol operation.\r\nThis causes the process to crash if it ever tries to allocate any memory using the modified heap HANDLE in the\r\nfuture.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 12 of 21\n\nA similar check of the heap’s ForceFlag field is shown below:\r\nIn the screenshot above, the sample finds the heap using the current process’s Process Environment Block\r\n(PEB). Then, it tests the ForceFlag field, which is at offset 0x44, against 0x40000000 to detect a debugger.\r\nThese checks are scattered around the sample’s logic near any heap operation. The easiest way to bypass these\r\nanti-debugging checks is to modify the process heap structures directly and reset both the Flag and\r\nForceFlag fields’ most significant byte to 0x00.\r\nThis sample also contains the following additional anti-debugging features:\r\nChecking beyond the bound of the allocated heap memory against magic constants like 0xABABABAB.\r\nThese magic constants come from a Windows feature that adds additional guardrails to heap memory to\r\nquickly detect memory corruption bugs. This feature is only enabled if the current process is running under\r\na debugger. This check can also be bypassed by modifying the Flag and ForceFlag fields of the heap.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 13 of 21\n\nCalling NtProtectVirtualMemory() and RtlEncryptMemory() to encrypt the\r\nDbgUiRemoteBreakin() function. This causes the current process to crash if there is any attempt to attach a\r\ndebugger afterwards. This does not have any effect if we start executing the sample using the debugger.\r\nCalling NtSetInformationThread() with ThreadHideFromDebugger (0x11) for the current thread. This call\r\nonly happens a few times at the beginning of the execution flow. A quick way to bypass this is patching the\r\nfunction to simply return NT_SUCCESS (0x00).\r\nTo avoid leaking capabilities and being tracked using the import hash, this sample manually resolves Windows\r\nAPIs using the PEB.\r\nThe PEB contains all the properties of its associated process, including a list of loaded DLLs. The sample can\r\nwalk this list of DLLs and their export tables to manually find addresses of the necessary Windows APIs. To\r\nfurther avoid leaking strings,  the sample manually resolves Windows APIs using a hashing algorithm shown\r\nbelow:\r\nThe sample applies the hashing algorithm above on the DLLs and their export names to find a match instead of\r\ncomparing strings normally. However, in addition to the “Addition-Rotate Right 13” operation, the sample also\r\nXORs the result with the 0x10035FFF constant. This results in a set of API hashes that are different from other\r\nmalware families using a similar technique.\r\nThe sample doesn’t use the resolved APIs directly. Instead, for each API, it allocates a small memory chunk and\r\nbuilds a small piece of trampoline code which calculates and jumps to the target API.\r\nFor example, instead of executing a standard indirect call to NtOpenProcess() as an import, the sample calls to a\r\npointer at address 0x00fc5474, which points to a function at address 0x00330bc8 on the heap. This function is\r\nshown below:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 14 of 21\n\nAfter the rol operation, eax becomes 0x7743FC50, which is the address of ntdll!NtOpenProcess(). This makes\r\nstatic analysis significantly more tedious. We would have a hard time tracking all the calls to the trampoline code.\r\nBecause we can bypass the anti-debugging checks, we can use a debugger to help us automate the renaming of the\r\ntrampoline calls. The logic to resolve APIs and setup the trampoline code is at 0x00FA5dA0. Using the IDA Free\r\ndebugger, we can set a breakpoint at 0x00FA5DDB, which is the instruction right after the call to manually\r\nresolve Windows APIs. Then, we can edit the breakpoint to execute the following one-liner:\r\nfprintf(fopen(\"out.txt\", \"a+\"),\r\n \"%s\\n\",\r\n sprintf(\"%a -- %s\",\r\n GetRegValue(\"edi\"),\r\n get_name(GetRegValue(\"eax\"))\r\n )\r\n)\r\nThis small snippet tells the IDA Free debugger to log the following items to the file “out.txt” in the current\r\nworking directory:\r\nThe current value of the edi register. This is the address of the trampoline code. In our example, this would\r\nbe 0x00FA5dA0.\r\nThe name of the value in the eax register. The eax register holds the address of the resolved API. In our\r\nexample, eax would be 0x7743FC50. Within the current process context, this is the address of\r\nntdll!NtOpenProcess().\r\nOnce the breakpoint is ready, we can let the sample execute through all the API resolution logic. At the end, we\r\nshould see the out.txt file that looks similar to this:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 15 of 21\n\nAfter the sample finishes resolving all the APIs, we can dump the current process including all of its allocated\r\nmemory for further analysis. Then, we can write a small IDA script to parse out.txt and rename all the trampoline\r\ncalls to the appropriate APIs. This will help speed up our static analysis significantly. An example of such a script\r\nis available in Appendix D.\r\nThe leaked LockBit v3 builder generated the encryptor and decryptor for Windows. Although the decryptor can\r\nrun on Linux using Wine, Calif decided to re-implement the decryption logic in C for the following reasons:\r\nWe want to run the decryptor natively on VMWARE ESXi.\r\nWe want to confirm our understanding of the encryption scheme.\r\nWe want to avoid executing the malware author’s decryptor which may contain other data corruption bugs.\r\nOther affected organizations may also find our decryptor useful.\r\nThis section describes how we build a decryption tool for Linux. The tool is open-source and can be downloaded\r\nfrom GitHub.\r\nCalif identified the two crypto functions to be Salsa20 (0x00FA20AC) and RSA with no padding (0x00FA17B4).\r\nInitially, instead of fully reverse-engineering these functions, we take the code directly from the binary and run it\r\nas shellcode inside a C wrapper. Calif’s decisions were based on the following reasons:\r\nIt would take us too long to fully analyze and confirm the algorithms.\r\nThe sample uses a custom implementation of the two algorithms. Therefore, re-implementation or using a\r\nstandard library may introduce discrepancies and bugs.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 16 of 21\n\nWe extract the following items directly from the ransomware into shellcode that we can call using our wrapper:\r\nThe Salsa20 encryption function and related functions\r\nThe Raw RSA function and related functions\r\nThe checksum calculation algorithm\r\nThe APLib compression function and related functions\r\nWhen preparing these functions, we also fix any absolute address references so we can call them correctly in our\r\nwrapper without causing a crash.\r\nThe sample is compiled for a 32-bit Windows environment. To get the shellcode to run correctly, we also need to\r\ncompile our code for this environment. By default, GCC would default to the cdecl calling convention, but\r\nWindows uses stdcall. We fix that by adding the attribute (__attribute__((stdcall)).\r\nThe data section of an executable is marked as non-executable, therefore we can not execute the shellcode directly\r\nfrom there. Instead, we allocate new memory pages with executable permission and copy the shellcode over.\r\nThe following IOCs come from our specific build of this variant of LockBit v3. Here are the components that may\r\nbe different across different builds:\r\nThe unique ID for this build: IzYqBW5pa.\r\nFile hashes other than the hash of the icon and desktop background.\r\nFilename: LB3.exe\r\nFile type: Windows Portable Executable (PE) x86\r\nFile size: 156,160\r\nSHA256 hash: f34dd8449b9b03fedde335f8be51bdc7f96cda29a2dde176c3db667ba0713c6f\r\nFilename: LB3Decryptor.exe\r\nFile type: Windows Portable Executable (PE) x86\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 17 of 21\n\nFile size: 33,280\r\nSHA256 hash: 8f0a2d5b47441fbcf1882aa41cae22fd0db057ccc38abad87ccc28813df3a83c\r\nVolatile:\r\nWhen configured, the sample creates the following mutex:\r\nGlobal\\a91a66d6abc26041b701bf8da3de4d0f where a91a66d6abc26041b701bf8da3de4d0f is\r\ncalculated from the embedded RSA private key\r\nFiles\r\nFilename: C:\\ProgramData\\IzYqBW5pa.ico where IzYqBW5pa is the unique ID for this specific\r\nvariant.\r\nFile type: ICO\r\nFile size: 15,086\r\nSHA256 hash: 95e059ef72686460884b9aea5c292c22917f75d56fe737d43be440f82034f438\r\nFilename: C:\\ProgramData\\IzYqBW5pa.bmp.\r\nFile type: BMP\r\nFile size: 86,708\r\nSHA256 hash: ef66e202c7a1f2a9bc27ae2f5abe3fd6e9e6f1bdd9d178ab510d1c02a1db9e4f\r\nFilename: IzYqBW5pa.README.txt.\r\nFile type: TXT\r\nFile size: 6,197\r\nSHA256 hash: af23f7d2cf9a263802a25246e2d45eaf4a4f8370e1b6115e79b9e1e13bf20bfe\r\nRegistry:\r\nPath: HKEY_CLASSES_ROOT\\.IzYqBW5pa\\DefaultIcon\r\nValue: C:\\ProgramData\\IzYqBW5pa.ico\r\nWhen configured, the sample communicates with the configured C2 server using HTTP Protocol\r\nPOST method. This specific variant is not configured with a C2 server.\r\nWhen communicating with the C2 server, the sample uses the following User-Agent string:\r\nChrome/91.0.4472.77.\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 18 of 21\n\nCommunication with the C2 server is encrypted using the AES algorithm. This specific variant is not\r\nconfigured to communicate with the C2 server. Therefore, it also does not contain the AES key.\r\n#include \u003cidc.idc\u003e\r\nstatic process(line) {\r\n // example line: .data:00FC5410 -- ntdll_RtlCreateHeap\r\n auto idx = strstr(line, \" -- \");\r\n // saddr: .data:00FC5410\r\n auto saddr = substr(line, 0, idx);\r\n // name: ntdll_RtlCreateHeap\r\n auto name = substr(line, idx + 1, -1);\r\n // old saddr: .data:00FC5410\r\n // new saddr: 00FC5410 as a string\r\n // addr : 0x00FC5410\r\n auto _idx = strstr(saddr, \":\");\r\n saddr = substr(saddr, _idx + 1, -1);\r\n auto addr = xtol(saddr);\r\n // old name: ntdll_RtlCreateHeap\r\n // new name: RtlCreateHeap\r\n _idx = strstr(name, \"_\");\r\n name = substr(name, _idx + 1, -1);\r\n auto len = strlen(name);\r\n // NULL terminate the last byte\r\n name[len-1] = '\\0';\r\n Message(\"Addr: 0x%x, name: %s\\n\", addr, name);\r\n set_name(addr, name, SN_NOCHECK|SN_FORCE);\r\n}\r\nstatic load_file() {\r\n auto fd = fopen(\"out.txt\", \"r\");\r\n auto line = readstr(fd);\r\n while (value_is_string(line)) {\r\n process(line);\r\n line = readstr(fd);\r\n }\r\n fclose(fd);\r\n return 0;\r\n}\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 19 of 21\n\nstatic main() {\r\n load_file();\r\n}\r\nEach chunk of the file belongs to one of the three groups (before group, skip group, after group). But for the sake\r\nof simplicity, let’s only consider the state of each chunk: encrypted (before group or after group), or unencrypted\r\n(skip group).\r\nTo determine if a chunk needs encrypting or decrypting, we can use the following algorithm:\r\nchunk_state = ''\r\n# first 'before_chunk_count' chunks belong to before group and are encrypted\r\ncrypt_chunk_count = before_chunk_count\r\nskip_chunk_count = (skipped_bytes / 0x20000) -1\r\nskip_count = skip_chunk_count\r\nfor chunk in chunks:\r\nif (crypt_chunk_count):\r\nchunk_state = \"en(de)crypt\"\r\ncrypt_chunk_count = crypt_chunk_count - 1\r\nelse:\r\nchunk_state = \"skip\" # belongs to a skip group\r\nskip_count = skip_count - 1\r\nif (skip_count == 0):\r\ncrypt_chunk_count = after_chunk_count\r\nThe decrypted file_encryption_info contains the value for before_chunk_count, after_chunk_count and\r\nskipped_bytes. To see how and where they are generated, refer to the File encryption section. The sample\r\ndetermines the chunk count based on the file size as described in the following table:\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 20 of 21\n\nSource: https://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nhttps://blog.calif.io/p/dissecting-lockbit-v3-ransomware\r\nPage 21 of 21",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blog.calif.io/p/dissecting-lockbit-v3-ransomware"
	],
	"report_names": [
		"dissecting-lockbit-v3-ransomware"
	],
	"threat_actors": [],
	"ts_created_at": 1775434667,
	"ts_updated_at": 1775791229,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/5e6f1b1978b9421e4a98d3ccecb9a777d5bec829.pdf",
		"text": "https://archive.orkl.eu/5e6f1b1978b9421e4a98d3ccecb9a777d5bec829.txt",
		"img": "https://archive.orkl.eu/5e6f1b1978b9421e4a98d3ccecb9a777d5bec829.jpg"
	}
}