{
	"id": "489f1969-6fc4-45b8-8478-2e36ec82f650",
	"created_at": "2026-04-06T01:30:30.441818Z",
	"updated_at": "2026-04-10T03:27:57.377681Z",
	"deleted_at": null,
	"sha1_hash": "c97dcce8c24f07a22ef31d8b985f202c6d023e7e",
	"title": "Decrypting Encrypted files from Akira Ransomware (Linux/ESXI variant 2024) using a bunch of GPUs",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 2920649,
	"plain_text": "Decrypting Encrypted files from Akira Ransomware (Linux/ESXI\r\nvariant 2024) using a bunch of GPUs\r\nPublished: 2025-03-13 · Archived: 2026-04-06 00:52:52 UTC\r\nI recently helped a company recover their data from the Akira ransomware without paying the ransom. I’m sharing\r\nhow I did it, along with the full source code.\r\nUpdate: since this article was written, a new version of Akira ransomware has appeared that can’t be\r\ndecrypted with this method\r\nThe code is here: https://github.com/yohanes/akira-bruteforce\r\nTo clarify, multiple ransomware variants have been named Akira over the years, and several versions are currently\r\ncirculating. The variant I encountered has been active from late 2023 to the present (the company was breached\r\nthis year).\r\nThere was an earlier version (before mid-2023) that contained a bug, allowing Avast to create a decryptor.\r\nHowever, once this was published, the attackers updated their encryption. I expect they will change their\r\nencryption again after I publish this.\r\nhttps://decoded.avast.io/threatresearch/decrypted-akira-ransomware\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 1 of 25\n\nYou can find various Akira malware sample hashes at the following URL:\r\nhttps://github.com/rivitna/Malware/blob/main/Akira/Akira_samples.txt\r\nThe sample that matches my client’s case is:\r\nbcae978c17bcddc0bf6419ae978e3471197801c36f73cff2fc88cecbe3d88d1a\r\nIt is listed under the version: Linux V3. The sample can be found on virus.exchange (just paste the hash to\r\nsearch).\r\nNote that the ransom message and the private/public keys will differ.\r\nWe do this not because it is easy, but because we thought it would be easy\r\nI usually decline requests to assist with ransomware cases. However, when my friend showed me this particular\r\ncase, a quick check made me think it was solvable.\r\nFrom my initial analysis, I observed the following:\r\nThe ransomware uses the current time (in nanoseconds) as a seed.\r\nOn my Linux machine, file modification times have nanosecond resolution.\r\nThey provided a screenshot of a partial log ( shell.log ), showing when the ransomware was executed,\r\nwith millisecond resolution.\r\nBased on this, my initial thought was: “This should be easy—just brute-force it by looking at the file timestamps.\r\nHow hard can it be?”\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 2 of 25\n\nI’ll explain in more detail, but it turned out to be more complicated than expected:\r\nThe malware doesn’t rely on a single moment in time but uses four moments, each with nanosecond\r\nresolution. The fist two and last two are related, so we can’t just bruteforce the time one by one. Key\r\ngeneration is complex, involving 1,500 rounds of SHA-256 for each timestamp. Each file ends up with a\r\nunique key.\r\nThe VMware VMFS filesystem only records file modification times with second-level precision.\r\nNot all ESXi hosts have millisecond resolution in their log files, some only log with second-level\r\nprecision. I am still unsure what configuration file causes this different behavior\r\nThe malware uses multiple threads during execution.\r\nThe file modification time reflects when the file is closed, not when it is opened for writing.\r\nReverse Engineering\r\nThe code is written in C++, which is notoriously difficult to read, but fortunately, it wasn’t obfuscated. The binary\r\nis statically linked (a bit harder to analyze), but all strings are in cleartext. The error messages indicate that the\r\nNettle library is used, which made understanding the code much easier.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 3 of 25\n\nThe existence of error strings really helps\r\nThe code to generate random is like this (the actual code is in 0x455f40 in the binary)\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\nvoid generate_random(char *buffer, int size)\r\n{\r\nuint64_t t = get_current_time_nanosecond();\r\nchar seed[32];\r\n//in the real code, it uses C++ code to convert int to string\r\nsnprintf(seed, sizeof(seed), \"%lld\", t);\r\nstruct yarrow256_ctx ctx;\r\nyarrow256_init(\u0026ctx, 0, NULL);\r\nyarrow256_seed(\u0026ctx, strlen(seed), seed);\r\nyarrow256_random(\u0026ctx, size, buffer);\r\n}\r\nThe random generator is implemented in yarrow256.c . Here is the relevant code, with unnecessary parts\r\nremoved. As noted in the comments:\r\nThe number of iterations when reseeding, P_t in the yarrow paper. Should be chosen so that reseeding\r\ntakes on the order of 0.1-1 seconds.\r\n1\r\n2\r\n3\r\nvoid\r\nyarrow256_seed( struct yarrow256_ctx *ctx,\r\nsize_t length,\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 4 of 25\n\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n27\r\n28\r\n29\r\nconst uint8_t *seed_file)\r\n{\r\nsha256_update(\u0026ctx-\u003epools[YARROW_FAST], length, seed_file);\r\nyarrow256_fast_reseed(ctx);\r\n}\r\nvoid\r\nyarrow256_fast_reseed( struct yarrow256_ctx *ctx)\r\n{\r\nuint8_t digest[SHA256_DIGEST_SIZE];\r\nunsigned i;\r\nsha256_digest(\u0026ctx-\u003epools[YARROW_FAST], sizeof (digest), digest);\r\nyarrow_iterate(digest);\r\naes256_set_encrypt_key(\u0026ctx-\u003ekey, digest);\r\nmemset (ctx-\u003ecounter, 0, sizeof (ctx-\u003ecounter));\r\naes256_encrypt(\u0026ctx-\u003ekey, sizeof (ctx-\u003ecounter), ctx-\u003ecounter, ctx-\u003ecounter);\r\n}\r\n#define YARROW_RESEED_ITERATIONS 1500\r\nstatic void\r\nyarrow_iterate( uint8_t *digest)\r\n{\r\nuint8_t v0[SHA256_DIGEST_SIZE];\r\nunsigned i;\r\nmemcpy (v0, digest, SHA256_DIGEST_SIZE);\r\nfor (i = 0; ++i \u003c YARROW_RESEED_ITERATIONS; )\r\n{\r\nuint8_t count[4];\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 5 of 25\n\n30\r\n31\r\n32\r\n33\r\n34\r\n35\r\n36\r\n37\r\n38\r\n39\r\n40\r\n41\r\n42\r\n43\r\n44\r\n45\r\n46\r\n47\r\n48\r\n49\r\n50\r\n51\r\n52\r\n53\r\n54\r\n55\r\nstruct sha256_ctx hash;\r\nsha256_init(\u0026hash);\r\nWRITE_UINT32(count, i);\r\nsha256_update(\u0026hash, SHA256_DIGEST_SIZE, digest);\r\nsha256_update(\u0026hash, sizeof (v0), v0);\r\nsha256_update(\u0026hash, sizeof (count), count);\r\nsha256_digest(\u0026hash, SHA256_DIGEST_SIZE, digest);\r\n}\r\n}\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 6 of 25\n\nSeed and encryption\r\nThe ransomware calls the random generator four times:\r\n1\r\n2\r\n3\r\n4\r\ngenerate_random(chacha8_key 32);\r\ngenerate_random(chacha8_nonce, 16);\r\ngenerate_random(kcipher2_key, 16);\r\ngenerate_random(kcipher2_key, 16);\r\nEach generate_random call uses the current nanosecond timestamp as a seed. Therefore, there are four unique\r\ntimestamps that need to be identified. The ransomware generates different keys for each file.\r\nThese keys are then saved at the end of the file as a trailer, encrypted with RSA-4096 and padded using\r\nPKCS#11 padding.\r\nThe files are divided into N blocks, and a percentage of each block is encrypted. This percentage is defined by the\r\nransomware’s -n parameter. For each block:\r\nThe first 0xFFFF bytes are encrypted using KCipher2.\r\nThe remaining bytes are encrypted using Chacha8..\r\nThe following picture shows how a file is split. Note that, for very small files, knowing the Chacha8 key and IV\r\nisn’t necessary..\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 7 of 25\n\nAfter studying various VMware filetypes (I will go deeper into this later), I am convinced that the most important\r\nfiles (flat VMDK and sesparse files) has a fixed header, and I can use that to attack the encryption.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 8 of 25\n\nOther details\r\nAt this point, I didn’t analyze deeper. But I am sure that I can reverse engineer the rest of the algorithms later,\r\nspecifically:\r\nHow to split the file into blocks\r\nHow is the encryption performed across blocks, does it continue the stream?\r\nThese details will be important later. However, for now, if we can’t successfully brute-force the timestamps, none\r\nof the other steps will matter.\r\nBruteforce feasibility\r\nThe approach is as follows:\r\n1. Generate two timestamps ( t3 and t4 ).\r\n2. Convert these timestamps into seeds and generate random bytes.\r\n3. Use these bytes as the KCipher2 key and IV.\r\n4. Encrypt known plaintext and compare the result with the known ciphertext from the encrypted file.\r\nLets make a plan:\r\nCheck feasibility: Determine if brute-forcing is fast enough to be practical.\r\nIdentify the plaintext: Known plaintext is required for brute-forcing.\r\nEstimate the seed initialization time: We need to know when the encryption seed was initialized, at least\r\nwith second-level precision. This knowledge can reduce the brute-force scope to about 1 billion values.\r\nThe simplest (but inefficient) way is to try all possible timestamp pairs where T4 \u003e T3 . The number of possible\r\npairs is calculated as: N×(N−1)/2\r\nWith N = 1 billion , that results in 500 quadrillion possible pairs.\r\nWe need to optimize this. First we need to convert all the nanoseconds in a one-to random values:\r\nOn my mini PC CPU, I estimated a processing speed of 100,000 timestamp to random bytes calculations\r\nper second (utilizing all cores).\r\nThis means it would take about 10,000 seconds (under 3 hours) to convert all timestamps to seed values.\r\nOnce converted, these values can be saved for reuse.\r\nLater, I optimized the process using a GPU, reducing the conversion time from 3 hours to under 6\r\nminutes.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 9 of 25\n\nIf we have a completely deterministic machine, without any interruption, we can run the malware, measure it,\r\nknow the exact time between T3 and T4. But unfortunately we don’t have this:\r\nThe malware uses multiple threads,\r\nIt runs on a machine that is not idle, the distance between T3 and T4 varies based on the scheduler and how\r\nbusy the system at that time.\r\nThe code also calls a lot of C++ libraries, which allocates and deallocates objects and makes the execution\r\ntime more unpredictable.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 10 of 25\n\nTo be clear:\r\nwe need to enumerate t3 (1 billion values for each second)\r\nwe dont start at t3 + 1, but at t3 + start offset, since we know that seeding the value takes time (at least a\r\nmillion nanosecond on my machine), this is the “start offset“\r\nwe assume that it will only take a few million nanosecond to until the next code is executed (remember:\r\nthere can be interruptions because of the CPU scheduler, and there are several millions instructions\r\nexecuted). This is the “offset range” value\r\nWhat we can do is to try to run the exact same code as the malware, collect timing data, and try to find a range\r\nthat statistically makes sense. Using the same technique that I use on my previous post, instead of recreating the\r\nalgorithm and running it, I just modified the malware and tested on several local machines that I have. The\r\nruntime varies quite a lot between machines.\r\nMy friend Deny went to the datacenter and did the test on the real hardware that was infected. The result is: the\r\ntime range varies, and sometimes quite a lot. The normal range of the offset is around 2-4 million nanoseconds (so\r\nthe offset range is 2 million), but the value varies from 1.5 – 5 million (total offset range is 4.5 million).\r\nWe still need to enumerate 4.5 quadrillion pairs, but this appears to be doable. If we have a system capable of\r\nrunning 50 million encryptions per second, the process would take a few hundred days. However, with 16 such\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 11 of 25\n\nsystems, we could complete it in a few months on a CPU. By renting additional machines, we could speed up the\r\nprocess even further. Later, I optimized this using a GPU, achieving a significant speed improvement.\r\nI wasn’t sure about how fast we can do Kicpher2, but a quick comparison with chacha, and some quick\r\nbenchmarking shows that using CPU ony, I should be able to do at least millions of Kichper operations per second\r\non my machine.\r\nAs explained before, if t3 and t4 are correct, we will be able to decrypt the first 8 bytes of the file, and it will\r\ndecrypt to a known plaintext.\r\nNext lets check the feasibility of obtaining plaintext from different VMware files\r\nVMWare File Types\r\nFor each file, we need a plaintext sample: the first 8 bytes of the file for KCipher2 (offset 0) and another 8 bytes\r\nstarting from offset 65,535 (only for large files). Since each block of KCipher2 is 8 bytes, we should use an 8-byte\r\nplaintext. It is possible to use fewer bytes (by using bit masking), but this could increase the risk of false positives.\r\nFlat-VMDK\r\nThis is a raw disk file. If you’re lucky, this might be the only file you need to recover. However, if snapshots were\r\nmade (as in this client’s case), the new data would be written to sesparse files.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 12 of 25\n\nTo obtain the first 8 bytes of the flat VMDK, you’ll need to install the same OS that was used on the original VM.\r\nThere are several variations of bootloaders used by different OS versions.\r\nTo determine which OS was used, check the corresponding VMX file. It should contain partially readable\r\nplaintext, allowing you to inspect the configuration for “guestOS”. You might find something like:\r\nguestOS=”ubuntu”. However, ideally, you already have documentation regarding which OS was used for each\r\nVM, so you don’t have to rely on this method.\r\nFor the bytes at position 65,535 (plaintext for Chacha8), it is almost always guaranteed to be zero, since the\r\npartition typically starts at a later sector.\r\nSesparse\r\nIf you create snapshots for your VM, there will be a SESPARSE file for each snapshots. We can see the file format\r\nfrom the QEMU source code.\r\nhttps://github.com/qemu/qemu/blob/master/block/vmdk.c\r\nThe file header is 0x00000000cafebabe , and at position 65,535, it should be 0x0 (at least, that’s what I observed\r\nin my analysis).\r\nOther files\r\nOther files are not critical for restoring a working VM, but for initial testing, understanding the time distribution\r\ncan be helpful. If there are many small files with the same timestamp, it’s useful to know if they cluster within a\r\nspecific timestamp range.\r\nHere are some common file signatures to identify plaintexts:\r\nNVRAM files start with: 4d 52 56 4e 01 00 00 00\r\nVMDK files (disk descriptor) start with the string: # Disk Descriptor\r\n.VMX files start with: .encoding\r\nVMware log files have lines starting with the format: YYYY-MM-DD\r\nSince these files are partially readable, we can often guess the initial timestamp based on the beginning of\r\nthe file (e.g., the YYYY-MM- part of the log).\r\nBy identifying plaintexts in these files, the next step is to narrow down the timestamp for accurate brute-forcing.\r\nEncryption timestamp\r\nNow that we know brute-forcing is feasible and we have both plaintext and ciphertext, the next step is to\r\ndetermine when the encryption occurred for each file (since each file will have different keys).\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 13 of 25\n\nESXI log\r\nThe command used to run the malware is recorded in the shell.log file (including the setting for n, which defines\r\nhow much of the file should be encrypted).\r\nSome ESXi hosts provide millisecond resolution in their logs, while others only offer second-level precision. This\r\nlog gives us the initial timestamp for when the malware started.\r\nFor example, if the log shows that the malware started at 10:00:01.500, we can safely ignore the first 500 million\r\nnanoseconds when brute-forcing, which helps narrow down the search range.\r\nFilesystem timestamp and modification time\r\nUnfortunately, ESXi file systems do not support nanosecond precision.\r\nAnother challenge is that the file modification time is recorded only when the file is closed. This means the\r\nrecorded timestamp might not exactly reflect the moment when the encryption process started but rather when it\r\nended.\r\nIn Linux (using most filesystems), timestamp accuracy is nanosecond\r\nFor small files, encryption typically takes only a few milliseconds, so the timestamp will most likely reflect the\r\nexact second when the file was encrypted. The next step is to determine the encryption time for larger files, where\r\nthe process takes longer and the timestamps may be less precise.\r\nin VMFS, accuracy is second\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 14 of 25\n\nMultithreaded Encryption\r\nThe malware uses multithreading, where each file is processed in a new thread, with a pool of workers limited by\r\nthe number of CPU cores. This has both advantages and disadvantages.\r\nIf the malware targets a single directory and the number of files is less than the number of CPU cores, the process\r\nis straightforward—each file will have a timestamp that is very close to the others. On an ESXi machine, it’s\r\ncommon to have CPUs with a large number of cores (in this case, the server has 64 cores).\r\nWhen checking for timestamps using:\r\n1 find /vmfs/volumes -exec stat {} \\;\r\nwe should be able to identify small files that were encrypted first. During brute-forcing, we can then check\r\nmultiple files simultaneously for that specific moment in time.\r\nFiles processed first will have similar timestamps, but things become more complex for files processed later. For\r\nlarger files, encryption can take seconds to minutes, and the modification time will reflect when the file was\r\nclosed, which is significantly later than when the encryption key was actually generated.\r\nThe malware uses boost::filesystem for traversing directories and files. The iterator in boost::filesystem\r\nfollows the order returned by readdir , which is the same order observed when using commands like ls -f or\r\nfind . .\r\nLet’s consider an example where we have 4 CPU cores and 8 files. If the files are tiny (less than 1 KB, such as\r\nVMDK descriptor files), their processing is almost instantaneous (within milliseconds). Here’s how the processing\r\nmight look:\r\nThreads A, B, and C each find and process small files ( file_a , file_b , file_c ), while Thread D\r\nfinds a large file ( file_d ). All four files are processed immediately.\r\nOnce Threads A, B, and C complete, they begin processing the next set of files ( file_e , file_f ,\r\nfile_g ). However, these files are larger and require more processing time.\r\nWhile the other three threads are still working, Thread D finishes processing the large file_d and starts\r\nworking on the final file ( file_h ). As a result, the starting timestamp of file_h will align with the\r\ncompletion time of file_d .\r\nNow, imagine having hundreds of files—it becomes difficult to determine the exact processing order. However,\r\none consistent observation is that the encryption start time for a file is likely to be the same or very close to the\r\nmodification time of another file.\r\nThis is because, once a thread finishes processing and closes a file (thereby recording its modification time), it\r\nwill immediately start processing the next available file. This creates a sequence where the encryption start time of\r\none file is closely linked to the modification time of the previous file.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 15 of 25\n\nSo given few hundred files and plenty of CPU cores, we may only have a list of a few seconds where the malware\r\nwill start to generate the random keys.\r\nSo now we have the final part of the puzzle: we know when the encryption was performed.\r\nNetwork Filesystem\r\nWhile reviewing the client’s logs, I noticed some entries mentioning the use of NFS. However, after clarification,\r\nit was confirmed that NFS was used only for backups and was not affected. All relevant files were stored on local\r\ndisks on the server.\r\nIf a network filesystem had been used, it would have complicated the process. If the network time between\r\nsystems wasn’t perfectly synchronized, the timestamps might have been inaccurate or unreliable, further\r\ncomplicating the brute-force process.\r\nCreating the bruteforcer\r\nThe plan seemed solid, so the next step was to implement the code. I needed to confirm whether the encryption\r\nprocess worked exactly like the malware.\r\nTo test this, I patched the malware code to make the gettime function return a constant value of 0, ensuring\r\npredictable and consistent results during testing.\r\nKCipher2\r\nI focused on KCipher2 because not all files use the Chacha8 key, particularly small files. Although KCipher2 is a\r\nstandard encryption algorithm, it’s not widely known, and I couldn’t find an optimized implementation for it.\r\nDuring experimentation, I noticed that my results didn’t match the standard KCipher2 implementations available\r\nonline. It turned out that the malware included a slight modification in the initialization vector and the encryption\r\nprocess, specifically involving endian swapping.\r\nCUDA\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 16 of 25\n\nI’m not an expert in CUDA programming. About 10 years ago, I briefly experimented with it but couldn’t find a\r\npractical use case for the company I worked for at the time.\r\nTo accelerate development, I asked ChatGPT (o1) to port the code to CUDA. The code compiled successfully but\r\nproduced incorrect results. It turned out that ChatGPT had slightly modified the numbers in the constant tables.\r\nAfter manually correcting these values, the code began to work.\r\nAlthough the implementation ran, I suspected it was suboptimal, but I wasn’t able to get further optimization\r\nsuggestions from ChatGPT (o1). At that point, I had two options: spend more time optimizing the code or proceed\r\nwith the predicted offset range and refine the code along the way. I chose to start testing immediately and optimize\r\nas needed. Unfortunately, this approach turned out to be a waste of money, as it didn’t yield any successful results.\r\nAt the start of the project, I only had two RTX 3060 GPUs. One was dedicated to my Windows machine, so I\r\ncould only use one GPU on my Mini PC (connected externally via Oculink). To improve performance, I decided\r\nto purchase an RTX 3090. The price in Thailand was still reasonable compared to the 4090 or higher models.\r\nI tested the implementation by reading the key and IV from memory, encrypting zero blocks, and writing the\r\nresults back to memory. The performance was disappointing, achieving only around 60 million encryptions per\r\nsecond. At this rate, the entire process would take about 10 years, clearly too slow for practical recovery.\r\nManual Optimization\r\nI performed some manual optimizations by removing unnecessary code to improve performance:\r\nOnly the first block is needed for brute force, so there was no need to handle additional blocks.\r\nThe code was simplified to only encrypt blocks of zeroes, reducing unnecessary processing.\r\nSince only the first 8 bytes of the result were required, the rest of the output was ignored to minimize\r\ncomputation.\r\nShared Memory\r\nAfter researching CUDA optimizations for AES, I discovered that using shared memory significantly improves\r\nperformance, contrary to what ChatGPT suggested. Surprisingly, the extra steps involved in copying constant\r\nmemory data to shared memory were negligible in terms of overhead but resulted in the code running several\r\ntimes faster.\r\nAvoiding Memory Writes\r\nInitially, I performed encryption on the GPU and matching on the host (CPU). However, this approach was slow,\r\neven when executed in parallel:\r\ngenerate encryption on GPU\r\ncopy result to CPU\r\nPerform matching in a new thread and submit the next batch of work to the GPU.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 17 of 25\n\nI found it much faster to avoid writing to memory altogether. Instead, the matching process is handled directly on\r\nthe GPU, and no data is written to memory unless a match is found. This approach significantly reduced\r\nprocessing time and improved efficiency.\r\nMultiple files matching\r\nFor each t3 and t4 combination, a match can occur for any file that shares the same second-level timestamp (but\r\nwith different nanoseconds).\r\nTo improve efficiency, we can attempt to match multiple files simultaneously. However, if there are too many files\r\nto match, the process can slow down significantly. Currently, the number of files processed in parallel is\r\nhardcoded to 32 to maintain a balance between performance and efficiency.\r\nThe Loop\r\nI considered and implemented two ways to do the loop. For every t3 value, we could start a GPU kernel to check\r\nall offset ranges. However, this method is inefficient, as it would require launching the kernel a billion times,\r\nresulting in significant overhead..\r\nAlternatively, we can launch a GPU kernel for each offset. Each kernel would then perform the necessary checks.\r\nThis approach is much faster because it reduces the number of submissions to just the “offset range”, which is\r\naround 2 to 4.5 million jobs.\r\nBatch Checking\r\nInitially, my approach was to submit a task to the GPU, wait for the result using cudaDeviceSynchronize() , and\r\nthen submit the next batch of work. However, this method proved to be slow.\r\nSubmit work to the GPU, and if a match is found, simply mark it using a found flag.\r\nOnly call cudaDeviceSynchronize() to check results every 100 steps. If a match is found, the flag is reset\r\nto zero before proceeding.\r\nWhile this method significantly improved performance, there’s a slight possibility that if two offsets are very close\r\n(less than 100 steps apart), the code might miss one of them. Although this issue never occurred during my tests, I\r\nadded an optional mode of loop. In this mode, the program reads a list of offsets and ensures that nearby offsets\r\nare also checked manually to avoid missing any potential matches.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 18 of 25\n\nFinal Speed\r\nI believe that GPU experts could still find ways to further optimize my code. Currently, I’m achieving around 1.5\r\nbillion encryptions per second for KCipher2 on my RTX 3090.\r\nFor testing 1 billion values with a single offset, it takes about 0.7 seconds, including the time to check for\r\nmatches (with a maximum of 32 matches per batch).\r\nTesting 2 million offsets would require approximately 16 days on a single GPU, or just 1 day using 16\r\nGPUs.\r\nI also conducted tests using Runpod, and the RTX 4090 turned out to be the ideal option. Although it’s about 60%\r\nmore expensive than the 3090, it’s also 2.3 times faster.\r\nWith a 4090, the same process would take around 7 days on a single GPU.\r\nUsing 16 GPUs, the process could be completed in just over 10 hours.\r\nRunning the brute force\r\nFrom a cost perspective, the RTX 4090 is an excellent choice for this task due to several factors:\r\nLarge memory is not required.\r\nFloating-point operations are not needed.\r\nThe RTX 4090 offers a high number of CUDA cores, enhancing processing speed.\r\nThe rental price for an RTX 4090 is relatively low compared to other high-end GPUs.\r\nIf the 4090 is unavailable, the 3090 is also a good alternative considering its price-to-performance ratio.\r\nInitially, my client considered using Google Cloud Platform (GCP) machines and seeking a discount for a month-long rental. However, this option proved to be extremely expensive (costing tens of thousands of USD).\r\nAfter some research, I found more cost-effective alternatives: Runpod and Vast.ai.\r\nRunpod\r\nTo brute force 1 second (1 billion nanosecond), with offset range of 2 million, it will take 7 days. The cost for a\r\nRTX 4090 (at the time of this writing) is 0.69 USD/hour. It will cost around 116 USD to brute force a single\r\nsecond. Renting 16 GPUs will have the work finished in around 10 hours, same cost, but faster.\r\nBrute forcing with the range of 4.5 million (which is the range that we need) costs 261 USD. Depending on the\r\nnumber of encrypted files, you might need to brute force 10 or more seconds. If you have a lot of files to recover,\r\nweekly or monthly rent will be cheaper.\r\nNote: These costs assume everything is executed perfectly. Any mistakes or the need to repeat processes can\r\nsignificantly increase costs.\r\nIn total, including all my experiments and tests, I spent around $1,200.\r\nVast.ai\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 19 of 25\n\nUnlike runpod, when using vast.ai, you are renting a machine from some random person brokered by vast.ai.\r\nWhen doing the bruteforce, no sensitive data is sent, so privacy should not be a concern.\r\nUsing vast AI, the bruteforce cost can be reduced to half, but this depends on your luck in obtaining the machine.\r\nThe first few machines that I tested didn’t work (network timeout after around 10 minutes of waiting). I also had\r\nproblem with pulling docker images from docker.io (I had to select another template from another docker\r\nrepository).\r\nThe rest of the work\r\nNow that I found the value of t3 and t4, I can try to find the value for t1 and t2. The value of t1 must be less than\r\nt3, and the time offset is less than 10 million nanoseconds. This can be found quickly in minutes using a single\r\nGPU.\r\nBlock split algorithm\r\nHere is the algorithm used to split the file into parts:\r\nenc_block_size: for every parts/blocks, this is how many bytes to encrypt. The first 0xFFFFF will be\r\nencrypted using KCipher2, and the rest using Chacha8\r\npart_size: the size of the block\r\nencrypted_parts: how many blocks to encrypt\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\nvoid compute_blocks(uint64_t filesize,\r\nuint8_t percent,\r\nuint64_t *enc_block_size,\r\nuint64_t *part_size,\r\nuint64_t *encrypted_parts)\r\n{\r\nint parts = 3;\r\nif ( percent \u003e 49u )\r\nparts = 5;\r\nuint64_t enc_size = filesize * (uint64_t)percent / 100;\r\n*enc_block_size = enc_size / parts;\r\n*encrypted_parts = parts - 1;\r\n*part_size = (filesize - *enc_block_size * (*encrypted_parts)) / parts;\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 20 of 25\n\n14 }\r\nEncryption Detail\r\nThe malware uses the 8 rounds variant of Chacha called chacha8, not Chacha20 as many sites reported.\r\nFor kcipher2, we will encrypt the first 65535 bytes (yes, not 65536). It means that one byte will remain\r\nfrom the first block, and this needs to be used for the next block\r\nFor cacha20, we just throw away the rest of the encryption stream block when starting a new block\r\nRecovery Steps\r\nTo recover your files without paying, it is not as straightforward as running a generic decryptor. You will need to:\r\nobtain timestamps of your files\r\nobtain ciphertext and plaintext for your files\r\nrent GPUs\r\nNote about the code\r\nTo be honest, I originally wrote this code for one-time use, specifically for this particular client. The shared code\r\nis filled with experimental logic, quick hacks, and lacks proper testing.\r\nI don’t have the motivation to clean it up further, apart from removing some client-specific test cases and\r\ncomments. It’s functional for the intended purpose.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 21 of 25\n\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 22 of 25\n\nThe software I provided includes only the main brute-force and decryption components, intended to be used once\r\nyou have the necessary timestamps.\r\nI don’t have a dedicated system to manage multiple GPUs. Instead, I rely on basic shell scripting and a custom\r\nscript that sends a Telegram message when a match is found. The code is “good enough for me” and simply\r\n“works for me.”\r\nIn essence, you’ll need a capable system administrator who understands the process and knows how to manage\r\nand troubleshoot the system effectively.\r\nBuilding the code\r\nSee README.md in the repository, it also has a sample config file to test that it works. Sample encrypted files\r\nand configuration files are also provided.\r\nObtain timestamps\r\nI hope you haven’t touched the files, because all hope of recovery will be gone if the timestamps are unknown.\r\nUse stat filename to get the modification timestamp. Use find /vmfs/volumes -exec stat {} \\; \u003e\r\n/tmp/stats.txt to get the timestamp of everything.\r\nThe file shell.log can help to figure out the minimum timestamp to use.\r\nObtain ciphertexts\r\nObtain the ciphertext, as explained above:\r\nFor flat-vmdk, you need to extract this from the exact OS that you use (including the exact instalation\r\nmethod, e.g: using BIOS/UEFI)\r\nFor sesparse file, use the header 0x00000000cafebabe\r\nFor other files, see what I wrote above\r\nMeasure server speed\r\nYou can always just use an offset range of 1.5-5 million, but this may not be the correct range if your hardware is\r\ntoo fast or too slow. You can measure this by checking out the timing-patch-1 folder and timing-patch-2\r\nfolder on my github repository.\r\nThe first one only measures time ranges by calling the function directly. The second one is used to encrypt a\r\ndirectory, but it is patched so that it will write down the exact time when the timestamp is used as the seed to\r\n/tmp/log.bin .\r\nDivide the work\r\nCreate config files based on the ciphertext/plaintext and timestamp. You can create/split this manually, or use a\r\nscript to generate it. My code doesn’t do any error checking, make sure the timestamp is in nanosecond format,\r\nmake sure all plaintext and ciphertext values are correct.\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 23 of 25\n\nRent GPUs\r\nIf you want a very quick and easy setup, use runpod or other service. If you want to be cheap, use vast.ai, or run it\r\non your own hardware (~ 1K USD for one RTX 3090, which you can resell later).\r\nRun Kcipher2 bruteforce\r\nThe first brute force is to find t3 and t4 for Kcipher.\r\n./anti-akira run config.json \u003cgpuindex\u003e\r\nFor example:\r\n./akira-bruteforce run2 config.json\r\nAppend GPU index if you have multiple GPUs\r\n./akira-bruteforce run2 config.json 1\r\nI suggest running it inside tmux, so you will be fine in the event of network disconnect.\r\nIf we are lucky, output.txt will be generated for each t3/t4 found.\r\nAs explained above: this may take days (depending on GPU used), so please make sure:\r\nall the config files are good\r\nYou are using the correct GPU index\r\nmake sure everything is running\r\ncheck with nvidia-smi (with runpod, we can also view the GPU status using the web)\r\nmake a notification system to alert you if output.txt is created/updated\r\nRun chacha8 bruteforce\r\nThis is not necessary for small files, but it is neede for big files. For each offset found, generate a config with the\r\nt3 found in the previous step. On my target machine, the distance between t1 and t3 is less than 10 million, and the\r\nt1 to t2 is around 1.5 – 5 million. The brute force should only take around 10 minutes.\r\nDecrypt the files\r\nNote that the decryptor has the percentage hardcoded to 15 percent, so please change this before running the\r\ndecryptor in case the attacker uses different value.\r\nOnce we have obtained the t1, t2, t3, and t4, run the decryptor:\r\n./decrypt filename.vmdk \u003ct1\u003e \u003ct2\u003e \u003ct3\u003e \u003ct4\u003e\r\nThe decryption process is not optimized, so it will take a while to decrypt.\r\nConclusion\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 24 of 25\n\nProbably 99.9% of the time when you get a ransomware, it won’t be recoverable without the key. But if you are\r\nlucky, sometimes it is possible to find a solution. It took me much longer than I anticipated to solve this, I thought\r\nthat it would take a week, but it took me almost three weeks until we recover an entire set of VM files.\r\nI also would like to add that I found a reddit thread about akira ransomware , I wasn’t sure that the ransomware\r\nstrain that I have is the same as theirs, and that is the reason why I just continue my own research and to open\r\nsource it. I hope that my experience and code will be useful for someone else.\r\nEverytime I wrote something about ransomware (in my Indonesian blog), many people will ask for ransomware\r\nhelp. Many people can’t even find the ransomware executable (just the encrypted file, which is not useful). Just\r\nchecking if the ransomware is recoverable or not may take several hours with a lot of efforts (e.g: if the malware is\r\nobfuscated/protected). So please don’t ask me to do that for free.\r\nSource: https://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nhttps://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/\r\nPage 25 of 25",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://tinyhack.com/2025/03/13/decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus/"
	],
	"report_names": [
		"decrypting-encrypted-files-from-akira-ransomware-linux-esxi-variant-2024-using-a-bunch-of-gpus"
	],
	"threat_actors": [
		{
			"id": "8c8fea8c-c957-4618-99ee-1e188f073a0e",
			"created_at": "2024-02-02T02:00:04.086766Z",
			"updated_at": "2026-04-10T02:00:03.563647Z",
			"deleted_at": null,
			"main_name": "Storm-1567",
			"aliases": [
				"Akira",
				"PUNK SPIDER",
				"GOLD SAHARA"
			],
			"source_name": "MISPGALAXY:Storm-1567",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "910b38e9-07fe-4b47-9cf4-e190a07b1b84",
			"created_at": "2024-04-24T02:00:49.516358Z",
			"updated_at": "2026-04-10T02:00:05.309426Z",
			"deleted_at": null,
			"main_name": "Akira",
			"aliases": [
				"Akira",
				"GOLD SAHARA",
				"PUNK SPIDER",
				"Howling Scorpius"
			],
			"source_name": "MITRE:Akira",
			"tools": [
				"Mimikatz",
				"PsExec",
				"AdFind",
				"Akira _v2",
				"Akira",
				"Megazord",
				"LaZagne",
				"Rclone"
			],
			"source_id": "MITRE",
			"reports": null
		}
	],
	"ts_created_at": 1775439030,
	"ts_updated_at": 1775791677,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/c97dcce8c24f07a22ef31d8b985f202c6d023e7e.pdf",
		"text": "https://archive.orkl.eu/c97dcce8c24f07a22ef31d8b985f202c6d023e7e.txt",
		"img": "https://archive.orkl.eu/c97dcce8c24f07a22ef31d8b985f202c6d023e7e.jpg"
	}
}