{
	"id": "fdb6e1d2-7c8f-41bd-af10-6780c36629f9",
	"created_at": "2026-04-06T01:32:36.097653Z",
	"updated_at": "2026-04-10T03:21:00.880569Z",
	"deleted_at": null,
	"sha1_hash": "f6ab56a55bd5b041343d2502cbc0f767fd079116",
	"title": "Getting Rusty and Stringy with Luna Ransomware",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1522084,
	"plain_text": "Getting Rusty and Stringy with Luna Ransomware\r\nArchived: 2026-04-06 01:17:45 UTC\r\nSHA256: 1cbbf108f44c8f4babde546d26425ca5340dccf878d306b90eb0fbec2f83ab51\r\nVT download link\r\nTable of Contents\r\nFamily Introduction\r\nRust Strings\r\nString Slice: \u0026str\r\nString\r\nRust Strings Print\r\nLuna Strings\r\nIDA Land\r\nWriting Ransom Note\r\nSkips Files and Directories\r\nEncryption Scheme\r\nPeculiarities\r\nCapability Peculiarities\r\nExecution Peculiarities\r\nSummary\r\nReferences\r\nFamily Introduction\r\nThe Luna ransomware appeared in July 2022. Unlike its competitors, this threat targeted VMware ESXi instances\r\nfrom the day it started operating.\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 1 of 13\n\nRust Strings\r\nIn my experience as a malware analyst, I’ve been used to seeing ASCII and null-terminated strings in binaries. I\r\nwas content writing IDAPython scripts where I created strings by searching for ASCII and null characters. And\r\none fine day, I had a Rust binary on my plate which broke my scripts. I interviewed the Rust God about strings.\r\nHere’s how it went:\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 2 of 13\n\nFig. 1: Rust Strings\r\nString Slice: \u0026str\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 3 of 13\n\nString slice is the term for \u0026str type of strings. These kinds of strings may exist in the binary or on the stack or\r\nheap. They always reference UTF-8 characters and are immutable. Let’s consider this simple Rust program:\r\nfn main() {\r\n let str1: \u0026str = \"Hello World!\\n\";\r\n}\r\nFig. 2 shows a snap of the disassembly as seen in IDA Home 7.7:\r\nFig. 2: String Slice\r\nString slices are essentially a data structure containing the address of the slice and its length. Such structures are\r\nalso called fat pointers because they contain extra data besides just the memory address. Consider the following\r\nRust program which prints the size (in bytes) of the \u0026str type:\r\nuse std::mem::size_of;\r\nfn main() {\r\n println!(\"A \u0026str size in bytes: {}\", size_of::\u003c\u0026str\u003e());\r\n}\r\nOn execution, it prints:\r\nA \u0026str size in bytes: 16\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 4 of 13\n\nMy system architecture is x64, so the size of \u0026str , a fat pointer, is 16 bytes. The first 8 bytes is the memory\r\naddress of the actual string literal and the next 8 bytes represents the length of that string literal. The following\r\nstructure represents a string slice:\r\nstruct string_slice\r\n{\r\n _QWORD val;\r\n _QWORD len;\r\n};\r\nIDA detects the above structure as core::fmt::ArgumentV1 and is defined as:\r\nstruct core::fmt::ArgumentV1\r\n{\r\n core::fmt::_extern_0}::Opaque *value;\r\n core::result::Result\u003c(),core::fmt::Error\u003e (*formatter)(core::fmt::_extern_0}::Opaque *, core::fmt::Formatter *\r\n};\r\nAlthough IDA’s structure is of the correct size (16 bytes), it is not particularly readable. So, I replaced it with my\r\nstructure definition for better readability. Fig. 3 shows it in action.\r\nFig. 3: String Slice IDA Structure\r\nString\r\nThe next string type in Rust is String . These kinds of strings are allocated only on the heap and they are\r\nmutable.\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 5 of 13\n\nString is also a data structure. It contains the address of the slice, its length on the heap and also the capacity of\r\nthe heap region. Consider the following Rust program which prints the size (in bytes) of the String type:\r\nuse std::mem::size_of;\r\nfn main() {\r\n println!(\"A String size in bytes: {}\", size_of::\u003cString\u003e());\r\n}\r\nOn execution, it prints:\r\nA String size in bytes: 24\r\nMy system architecture is x64, so the size of String is 24 bytes. The first 8 bytes is the memory address of the\r\nstring slice; the next 8 bytes represents the length of that string literal and the last 8 bytes is the capacity of the\r\nmemory region in the heap. The capacity signifies the maximum number of bytes that the string can hold. If a\r\nlonger string is required, then reallocation occurs on the heap. The following structure represents a String :\r\nstruct String\r\n{\r\n _QWORD val;\r\n _QWORD len;\r\n _QWORD cap;\r\n};\r\nFor example, a String may be allocated on the heap having the following structure field values:\r\nval = \"Hello!\"\r\nlen = 6\r\ncap = 10\r\nIDA detects the above structure as alloc::string::String and is defined as:\r\nstruct alloc::string::String\r\n{\r\n alloc::vec::Vec\u003cu8,alloc::alloc::Global\u003e vec;\r\n};\r\nLet’s consider this simple Rust program:\r\nfn main() {\r\n let str1: String = String::from(\"Hello World! 🙏\\n\");\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 6 of 13\n\n}\r\nFig. 4 shows a snap of the disassembly as seen in IDA Home 7.7. Here, v1 is the String variable.\r\nFig. 4: String IDA Structure\r\nFig 5. shows a snap of the UTF-8 encoding of the string literal:\r\nFig. 5: String UTF-8 Encoding\r\nRust Strings Print\r\nIt can be seen in Fig. 4 that there is no null character after the Hello World! 🙏\\n string. This can make reading\r\nstrings in IDA decompilation difficult as seen in Fig. 2 where the next string has polluted the decompilation. I\r\nwrote an IDAPython script which prints Unicode strings found in a Rust-based binary. I’ve been unable to find an\r\nIDAPython function which can create UTF-8 strings.\r\nLuna Strings\r\nUsing the IDAPython script, I found interesting strings.\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 7 of 13\n\nFig. 6: String UTF-8 Encoding\r\nLuna\r\n.ini\r\n.exe\r\n.dll\r\n.lnk\r\nError while writing encrypted data to:\r\nError while writing public key to:\r\nError while writing extension to:\r\nError while renaming file:\r\nW1dIQVQgSEFQUEVORUQ/XQ0KDQpBbGwgeW91ciBmaWxlcyB3ZXJlIG1vdmVkIHRvIHNlY3VyZSBzdG9yYWdlLg0KTm9ib2R5IGNhbiBoZWxwIHlv\r\nError while writing note\r\nAES-NI not supported on this architecture. If you are using the MSVC toolchain, this is because the AES-NI metho\r\nInvalid AES key size.\r\nhost unreachable\r\nconnection reset\r\n/proc/self/exe\r\nopenserver\r\nwindows\r\nprogram files\r\nrecycle.bin\r\nprogramdata\r\nappdata\r\nall users\r\nEncrypting file:\r\nHow to use:\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 8 of 13\n\n-file /home/user/Desktop/file.txt (Encrypts file.txt in /home/user/Desktop directory)\r\n -dir /home/user/Desktop/ (Encrypts /home/user/Desktop/ directory)\r\nThe base64-encoded string decodes to the ransom note:\r\n[WHAT HAPPENED?]\r\nAll your files were moved to secure storage.\r\nNobody can help you, except us.\r\nWe have private key, we have your black shit.\r\nWe are strongly advice you to be interested in safety of your files, as we can show your real face.\r\n[WHAT DO WE NEED?]\r\nAdmission, respect and money.\r\nYour information costs money.\r\n[WHO ARE WE?]\r\nA little team of people who can show your problems.\r\n[HOW TO REACH AN AGREEMENT WITH YOU?]\r\nSend us a message with those e-mails:\r\ngivefishtoaman666@protonmail.com\r\ngivehooktoaman666@protonmail.com\r\nIDA Land\r\nWhen analyzing Rust binaries, there are some notes to keep in mind:\r\nUnlike C or C++-based binaries, it is not easy to navigate Rust-based binaries in a top-down approach, i.e.\r\nstart at the top and analyze your way down. This is because Rust adds a bunch of runtime code (error\r\nhandling, memory-safe management, etc.) that pollutes the disassembly.\r\nWithin the same Rust binary, there can exist multiple calling conventions.\r\nIDA (atleast Home 7.7) may not have the capability to identify Rust library functions, so they are all\r\nmarked as regular functions. You might end up analyzing code for 2 hours that ends up being runtime or\r\nlibrary code.\r\nThe previous IDAPython script comes in handy to identify points from where you can start analysis. I could\r\nnavigate to the string location in the .rodata segment, cross-reference to the source which loads that string and\r\nthen analyze that piece of code rather than starting at the top. I started my analysis with the code that references\r\nthe base64-encoded string of the ransom note. I hoped this would position me in the neighborhood of the code that\r\ndoes the encryption.\r\nWriting Ransom Note\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 9 of 13\n\nAs mentioned before, the binary contains the base64-encoded form of the ransom note. It decodes it and then\r\nwrites it into a file named readme-Luna.txt .\r\nFig. 7: Luna Ransom Note\r\nSkips Files and Directories\r\nLuna doesn’t encrypt files which have:\r\nsubstring Luna in their filenames\r\none of the following extensions:\r\n.dll\r\n.exe\r\n.lnk\r\n.ini\r\nLuna doesn’t encrypt files under directories which contain one of the following substrings:\r\nopenserver\r\nwindows\r\nprogram files\r\nrecycle.bin\r\nprogramdata\r\nappdata\r\nall users\r\nEncryption Scheme\r\nLuna employs an encryption scheme that is commonly found in the ransomware world. It leverages both\r\nasymmetric and symmetric cryptography, i.e., the key for the symmetric cryptography is derived is from\r\nasymmetric cryptography. It uses curve25519-dalek package for Elliptic-Curve Cryptography (ECC) and\r\ncrypto::aes module for AES-256 CTR-mode cryptography.\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 10 of 13\n\nLuna’s encryption scheme can be summarized as follows:\r\nIt generates a public and private key on Curve25519.\r\nThe binary also contains the threat actor’s Curve25519 public key. Using the generated private key and the\r\nthreat actor’s public key, the sample derives the 32-byte shared secret.\r\nThe shared secret is used as the key for AES-256 CTR-mode. The IV is a string slice (a 16-byte fat pointer)\r\npointing to a string literal, Luna .\r\nBoth the shared secret and the generated public key are zero’d in memory to prevent data leak. As I was writing\r\nthis, I remembered Javier Yuste’s Avaddon ransomware decryption tool which relied on key information being\r\navailable in memory. Perhaps, zero’ing key information in memory is Luna’s safeguard against such decryption\r\ntools.\r\nFile Encryption\r\nLuna encrypts 50,000 bytes of plaintext file contents at a time. Since AES is in CTR mode, i.e., a stream cipher,\r\nthe output ciphertext size is equal to the input plaintext size.\r\nFor the threat actor’s decryption tool to work, the ransomware binary has to store encryption-related information\r\nin the encrypted file. In this case, the threat actor would need two points of information per encrypted file:\r\nCurve25519 32-byte public key generated by the binary to calculate the shared secret used to encrypt the\r\nfile.\r\nThe IV value used by AES-256 CTR-mode encryption. To this end, the Luna binary stores the previously\r\ngenerated Curve25519 32-byte public key and the IV string literal, Luna to the end of the encrypted file.\r\nEach encrypted file is given the extension, .Luna .\r\nPeculiarities\r\nCapability Peculiarities\r\nWhen I hear of ransomware targeting VMware ESXi, I usually come across capability in the binary to shut down\r\nrunning VMs. This helps in clean encryption of files. However, Luna doesn’t seem to contain any such capability\r\nwhich may result in encrypted files being corrupted and incapable of being recovered.\r\nExecution Peculiarities\r\nI was wrapping up this article when I noticed a few peculiarities in Luna’s execution.\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 11 of 13\n\nFig. 8: Execution Peculiarities\r\nSummary\r\nIn this article, we looked at a sample of Luna ransomware.\r\nIt used an encryption scheme of Curve25519 (asymmetric cryptography) and AES-256 CTR-mode\r\n(symmetric cryptography).\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 12 of 13\n\nUnlike popular ransomware families like BlackBasta and BlackCat, Luna doesn’t use intermittent\r\nencryption strategies and encrypts the entire file.\r\nCurve25519 public key and AES-256 IV values are stored at the end of the encrypted file.\r\nLike most ransomware families, certain files and directories are not encrypted.\r\nI also introduced you to strings in Rust. I presented an IDAPython script that prints UTF-8 strings found in\r\nthe .rodata segment of a Rust-based binary. These results gave a start point for our analysis.\r\nFinally, we looked at a few peculiarities (or bugs) in Luna’s code which makes for buggy directory\r\ntraversal.\r\nIn summary, Luna is the typical ransomware but with bugs and no optimizations.\r\nReferences\r\nhttps://doc.rust-lang.org/rust-by-example/std/str.html\r\nWhat is a fat pointer?\r\nhttps://docs.rs/c_str/latest/c_str/\r\nExploring Strings in Rust\r\nUnderstanding String and \u0026str in Rust\r\nStrings in Rust FINALLY EXPLAINED!\r\nA Deep Dive into X25519\r\nSource: https://nikhilh-20.github.io/blog/luna_ransomware/\r\nhttps://nikhilh-20.github.io/blog/luna_ransomware/\r\nPage 13 of 13",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://nikhilh-20.github.io/blog/luna_ransomware/"
	],
	"report_names": [
		"luna_ransomware"
	],
	"threat_actors": [],
	"ts_created_at": 1775439156,
	"ts_updated_at": 1775791260,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f6ab56a55bd5b041343d2502cbc0f767fd079116.pdf",
		"text": "https://archive.orkl.eu/f6ab56a55bd5b041343d2502cbc0f767fd079116.txt",
		"img": "https://archive.orkl.eu/f6ab56a55bd5b041343d2502cbc0f767fd079116.jpg"
	}
}