{
	"id": "4e8de282-5c2b-4934-a96c-7806a8c29c0d",
	"created_at": "2026-04-10T03:20:45.805909Z",
	"updated_at": "2026-04-10T03:22:18.425703Z",
	"deleted_at": null,
	"sha1_hash": "f5d3d9bd646ba2901eaea60862d5e93fd471c367",
	"title": "Remus: Unmasking The 64-bit Variant of the Infamous Lumma Stealer",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 8847361,
	"plain_text": "Remus: Unmasking The 64-bit Variant of the Infamous Lumma\r\nStealer\r\nBy Written by Vojtěch Krejsa, Jan Rubín\r\nArchived: 2026-04-10 03:00:10 UTC\r\nKey points\r\nGen Threat Labs has identified Remus, a new 64-bit infostealer we attribute to the infamous Lumma\r\nStealer family – emerging in the wake of Lumma’s takedown and the doxxing of its alleged core members.\r\nIn this technical blog post, we detail the compelling evidence tying Remus to Lumma across multiple\r\ndimensions.\r\nWe also describe a previously undocumented Application-Bound Encryption bypass employed specifically\r\nby Remus and Lumma.\r\nThe first Remus campaigns date back to February 2026, with the malware switching from Steam/Telegram\r\ndead drop resolvers to EtherHiding and employing new anti-analysis checks. \r\nIntroduction\r\nWhen the security industry talks about information stealers, Lumma Stealer, without a doubt, has become the\r\nnotorious icon of this landscape. Not only could it count itself among the most sophisticated, technically\r\nadvanced, and widespread stealers-as-a-service in the world, but it was also described in a variety of blog posts\r\nfrom basically everyone in the industry, including us.\r\nIn this analysis, we describe a new variant of Lumma Stealer that we call Remus, a new x64 build which we\r\nsuspected might occur in the foreseeable future after the doxxing of Lumma authors from late August to October\r\n2025. And the future is here.\r\nRemus brings the same stealing arsenal to the table as we already know from Lumma, capable of stealing stored\r\nbrowser passwords, cookies, cryptocurrency, and much more. However, rather than revisiting Lumma's well-documented capabilities, we focus on the remarkable resemblance between Remus and Lumma, as well as the new\r\ntechniques Remus introduces, including the use of EtherHiding to resolve C2s, replacing the traditional use of\r\nSteam and Telegram dead drop resolvers, and additional anti-analysis checks. \r\nThe attribution of Remus to Lumma is many-fold and described thoroughly throughout the blog post. The main\r\nindicators, to name a few, are the use of the same string obfuscation technique, AntiVM checks, direct\r\nsyscall/sysenter handling, indirect control flow obfuscation, and, most importantly, an almost identical approach to\r\nbypassing AppBound Encryption, which we’ve only seen used explicitly by Lumma to date.\r\nWith that said, we can still see active Lumma campaigns all around the world, which makes Remus not a\r\nreplacement, but a continuous evolution.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 1 of 19\n\nDevelopment timeline\r\nBefore diving into the technical details, it is worth noting that during our hunting efforts, we came across several\r\ntest samples, which even carry a testbuild label embedded directly in the binaries (see Figure 1). We are\r\ninternally referencing these builds as Tenzor (based on encrypted strings). These test builds are already 64-bit and\r\nstructurally very close to Remus, suggesting they represent a transitional step, or perhaps even a testing ground,\r\nbetween Lumma and Remus.\r\nFigure 1: The string testbuild present in a Tenzor sample. Reference sample:\r\n0580ebf601989457f0708799b431fd4d9f5e59d98838282d72936099aa6636da.\r\nThis also brings us to the name itself. Both Tenzor and Remus have left a string artifact in the same part of the\r\ncode, referencing the # TENZOR LOG and # REMUS LOG strings. Since we have been tracking only test builds with\r\nthe Tenzor string and Remus is the variant that is being actively distributed in live campaigns, we took the liberty\r\nof naming the new x64 Lumma variant after the latter string – Remus.\r\nFigure 2: Decrypted LOG strings in Tenzor (left) and Remus (right). Reference samples:\r\n0580ebf601989457f0708799b431fd4d9f5e59d98838282d72936099aa6636da (Tenzor),\r\ndbf6facd28406361a6a81417b3ff5eb272ccc8dcc58a36bd5335a253ae4bf036 (Remus).\r\nNotably, all the Tenzor samples carry a build date of September 16, 2025, in their stack-encrypted strings, which is\r\nmonths before the first Remus samples began appearing in the wild at the turn of January and February 2026. The\r\nbuild timing closely coincides with a period when Lumma Stealer suffered a major blow through a doxxing\r\ncampaign that exposed alleged core members and severely disrupted its operations. This may suggest that some of\r\nLumma’s authors split off, or that Lumma decided to rebrand under a new name in the midst of the doxxing\r\nfallout. Nevertheless, at Gen Threat Labs, we track both Remus and Tenzor as variants of Lumma Stealer. That\r\nsaid, the only samples bearing the Tenzor name are the ones that also carry the testbuild label. All others are\r\nidentified as Remus.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 2 of 19\n\nFigure 3: Development timeline.\r\nSimilarities between Remus and Lumma\r\nWhen we first started analyzing Remus, something felt familiar, and for good reason. Many of the techniques and\r\ndesign patterns we encountered in Remus closely mirrored those we had already seen in Lumma Stealer. The main\r\ndifference is that Lumma was 32-bit while Remus is 64-bit, which naturally introduced some variations. Yet, when\r\nwe looked past the 32-bit versus 64-bit differences and stripped away the obfuscation layers, what remained was\r\nstrikingly familiar.\r\nIn the following subsections, we highlight the most compelling evidence of a direct connection between Remus\r\nand Lumma. Note that this is not an exhaustive list. The overlaps are far more numerous, but we selected just a\r\nfew that we believe speak for themselves.\r\nForgotten dead drop resolver and string obfuscation \r\nFor a malware researcher, strings are among the most valuable resources during reverse engineering, and in the\r\ncase of attributing Remus to Lumma, they proved no different. \r\nBoth Remus and Lumma use a virtually identical mechanism for string obfuscation. The encrypted string is first\r\nassembled on the stack using a series of various forms of the mov instruction, followed by a decryption loop that\r\ntransforms the data byte by byte. The transformation is often further obscured by MBA (Mixed Boolean-Arithmetic) obfuscation and is either inlined or placed in a standalone function. In any case, each string is\r\nprotected using a unique transformation, making it very difficult to build a universal static decryptor. As a result,\r\nemulating the decryption loops is likely the most effective approach for dealing with encrypted strings in both\r\nRemus and Lumma. \r\nFigure 4: Decryption of the string “Processes.txt” in Remus (left) and Lumma (right). Reference samples:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69 (Remus),\r\n0683f353cf3e101f721f1658e2a554ff7888ff9f2c32e23ceb3d23876864a264 (Lumma).\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 3 of 19\n\nNotably, the decryption loops are often preceded by several nop instructions (Lumma) or a single nop\r\ninstruction encoded using multiple bytes (Remus), likely inserted as padding during a custom compilation pass.\r\nWe are aware that the compilers themselves can emit nop paddings. Still, in both Remus and Lumma, these\r\nsequences go beyond the typical, making it yet another indicator linking the two together.\r\nAfter decrypting all strings in both Remus and Lumma and comparing them side by side, we found roughly 100\r\ncompletely identical strings. Most of these, however, could be attributed to virtually any information stealer. The\r\nmore telling discovery came when we looked at the strings that were not identical – most of them turned out to be\r\nsemantically the same, just slightly refactored. This alone would already be a strong indicator. \r\nHowever, what ultimately confirmed our theory was shifting the string comparison from Lumma vs. Remus to\r\nLumma vs. Tenzor, as Tenzor effectively acts as a bridge between the other two – many of the strings we had\r\npreviously matched only semantically between Remus and Lumma were still present in Tenzor in their original,\r\nunmodified form, directly matching Lumma’s. At the same time, Tenzor also contained very specific strings found\r\nexclusively in Remus but not in Lumma, such as B9%????4rnO/@NQe?Nx* , used as a wildcard mask in the ABE-bypass (which we will discuss later), firmly linking it to both sides.\r\nFigure 5: Comparison of selected decrypted strings across Lumma, Tenzor, and Remus.\r\nOn top of that, among the decrypted Tenzor strings, we also found a Steam dead drop resolver\r\nhxxps[://]steamcommunity[.]com/profiles/76561199861614181 , which turned out to be the exact same resolver\r\npresent in multiple confirmed Lumma samples, namely:\r\n002f714f93bed53f165129a820c2d5b72227f1cafac43be19e5e223ce219a5e1 (Lumma)\r\n066c4ab954fc1270ee62c0d7c582c4c691e58e0ffef0c654bc204a46e440d16d (Lumma)\r\n0683f353cf3e101f721f1658e2a554ff7888ff9f2c32e23ceb3d23876864a264 (Lumma)\r\n0580ebf601989457f0708799b431fd4d9f5e59d98838282d72936099aa6636da (Tenzor)\r\nFigure 6: Decrypted Steam dead drop resolver in Tenzor. Reference sample:\r\n0580ebf601989457f0708799b431fd4d9f5e59d98838282d72936099aa6636da.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 4 of 19\n\nFigure 7: Decrypted Steam dead drop resolver in Lumma. Reference sample:\r\n0683f353cf3e101f721f1658e2a554ff7888ff9f2c32e23ceb3d23876864a264.\r\nApplication-bound encryption bypass\r\nAnother very strong indicator tying Remus and Lumma together is the way they bypass Application-Bound\r\nEncryption (ABE), as both employ a highly specific technique that, until now, we have only observed and have\r\nbeen actively tracking in Lumma Stealer.\r\nThe technique involves injecting a very short shellcode (less than 100 bytes) into the browser process to decrypt\r\nthe v20_master_key . However, unlike most techniques relying on injection, Lumma as well as Remus do not use\r\nthis shellcode to call IElevator::Decrypt with the key obtained from the Local State file (the [“os_crypt”]\r\n[“app_bound_encrypted_key”] JSON field). Instead, it locates the v20_master_key directly in the browser’s\r\nprocess memory, where it is stored in an encrypted form protected by CryptProtectMemory with the\r\nCRYPTPROTECTMEMORY_SAME_PROCESS flag. It then calls the complementary CryptUnprotectMemory with the same\r\nflag from within the browser’s context to decrypt it.\r\nIt is important to note that this in-memory encrypted form of the v20_master_key is protected with a different\r\nkey than the one used to protect the key on disk – it is a separate protection layer that Chromium browsers use to\r\nsafeguard the key while it resides in memory at runtime. However, since the key is protected with the\r\nCRYPTPROTECTMEMORY_SAME_PROCESS flag, decryption can only be performed by the same process that encrypted it,\r\nwhich is precisely why injection into the browser process is necessary in this specific bypass. For the curious, we\r\ndiscussed this topic in more detail in our recent VoidStealer blog post.\r\nFrom this point on, we will walk through the ABE bypass as implemented in Remus, noting any differences from\r\nLumma’s implementation where applicable. Where no differences are explicitly mentioned, the two\r\nimplementations work either identically or very similarly. \r\nSo how does Remus find the protected v20_master_key ? It begins by walking the browser’s PEB module list,\r\nlooking for two modules in a single pass: dpapi.dll and the browser DLL (e.g., chrome.dll ). \r\nWhen dpapi.dll is found, Remus manually parses its PE export directory, reading the DOS header, PE header,\r\nand export table through repeated NtReadVirtualMemory syscalls. For each exported function name, it computes\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 5 of 19\n\na seeded CRC32 hash and compares it against the pre-computed hash corresponding to  CryptUnprotectMemory .\r\nEssentially, a typical API-hashing, but in a remote process. Once a match is found, it resolves the function’s RVA\r\nto obtain its absolute address in the browser’s address space.\r\nWhen the browser DLL is found during the same pass, Remus decrypts a hex pattern\r\n488d058bcc8d02488901488b024889415b488d41 , which was encrypted by the same string obfuscation technique\r\ndescribed earlier and stores the value 0xEFF87 (which corresponds to 11101111111110000111 in binary) into a\r\nvariable we labeled as wildcard_mask (see Figure 8). \r\nFigure 8: Remus decrypting the hex pattern used in the ABE bypass. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nThe hex pattern corresponds to a sequence of opcodes that Remus searches for within the browser DLL (e.g.,\r\nchrome.dll ), while the wildcard mask, read from the least significant bit, indicates which bytes must match\r\nexactly and which should be treated as wildcards. Lumma does the same but takes a different approach to\r\nwildcarding. Instead of a hex mask, it uses a 20-character string B9%????4rnO/@NQe?Nx* , where ? denotes a\r\nwildcard and any other character means the byte must match exactly. Notably, this exact string also appears in\r\nTenzor builds, which directly ties Tenzor to the Lumma codebase.\r\nFigure 9: Visualization of the ABE-bypass pattern wildcarding (for Remus).\r\nLooking into chrome.dll , we can see that the pattern is designed to locate a LEA (Load Effective Address)\r\ninstruction that loads a 32-bit displacement pointing to the os_crypt_async::Encryptor virtual function table\r\n(vftable). The reason for specifically targeting the os_crypt_async::Encryptor class is that it holds the protected\r\nv20_master_key .\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 6 of 19\n\nFigure 10: The opcode pattern that Remus searches for within chrome.dll (the disassembly is from chrome.dll).\r\nOnce the pattern is found, Remus reads the 32-bit displacement at pattern_address + 3 and computes the\r\nabsolute address of the os_crypt_async::Encryptor vftable using the formula: target_addr = pattern_addr +\r\ndisp32 + 7 (since the LEA instruction is 7 bytes long and the displacement is relative to the next instruction). It\r\nthen scans the browser’s memory for this 8-byte vftable pointer by enumerating committed, readable memory\r\nregions via NtQueryVirtualMemory and reading each one into a local buffer via NtReadVirtualMemory .\r\nWherever a match is found, it marks the start of an os_crypt_async::Encryptor instance in memory, as the\r\nvftable pointer sits at the very beginning of the object. From there, extracting the protected v20_master_key is\r\njust a matter of walking the structure at known offsets. \r\nFigure 11: Remus finding the pattern in the browser process. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nAfter resolving all the necessary addresses, Remus allocates a buffer in the browser process via\r\nNtAllocateVirtualMemory , which serves as both the copy destination and the in-place decryption target. It then\r\nconstructs a 51-byte shellcode (shown in Figure 12), allocates a second buffer with the PAGE_EXECUTE_READWRITE\r\nprotection constant for the shellcode itself, and writes it into the browser process via NtWriteVirtualMemory . The\r\nshellcode is then executed via NtCreateThreadEx , and Remus waits for its completion using\r\nNtWaitForSingleObject . Once the v20_master_key is decrypted, Remus simply reads it from the buffer it\r\npreviously allocated (and, therefore, already knows its address) via NtReadVirtualMemory .\r\nFigure 12: The shellcode skeleton that Remus constructs for injection into the browser process(es).\r\nThe injected shellcode differs slightly between Remus and Lumma, but both fundamentally do the same thing –\r\ncopy the protected v20_master_key into a pre-allocated buffer and jump to CryptUnprotectMemory to decrypt it\r\nin place. Both construct the shellcode on the stack, patching in the resolved addresses before writing it into the\r\nbrowser process. The only real difference is that Remus produces a more compact variant (51 vs 62 bytes) by\r\nreusing registers. Concrete examples of the injected shellcodes are shown in Figure 13 (Remus) and Figure 14\r\n(Lumma).\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 7 of 19\n\nFigure 13: Example of Remus’s shellcode injected into Chrome. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nFigure 14: Example of Lumma’s shellcode injected into Chrome. Reference sample:\r\nb037fa1dd769891b538d9ca26131890c93e3458eec96c5354bdebe50d04a5b3d.\r\nAnother detail linking the two is that when no browser process is already running, or when injection into an\r\nexisting one fails, both Remus and Lumma spawn a new one on a separate, hidden desktop, preventing any visible\r\nwindows from appearing on the user’s screen. They first attempt to open an existing desktop using OpenDesktopW\r\nand fall back to creating a new one via CreateDesktopW if it does not yet exist. A new browser instance is then\r\nlaunched via CreateProcessW with STARTUPINFOW.lpDesktop set to this desktop. The only difference is that\r\nLumma uses a hardcoded desktop name  ChromiumDev (or previously also ChromeBuildTools ), while Remus\r\ngenerates a random 16-character alphanumeric string using a Mersenne Twister PRNG, a natural evolution rather\r\nthan a change in approach.\r\nFurthermore, both Remus and Lumma also employ SYSTEM token impersonation as an alternative method to\r\nbypass ABE. Interestingly, the two families differ in which method they prefer: Remus attempts SYSTEM\r\nelevation first and falls back to shellcode injection, while Lumma tries injection first and resorts to SYSTEM\r\nelevation only if it fails. That said, SYSTEM token impersonation for ABE bypass is a well-known technique\r\nwidely used across many stealer families. What, however, is highly distinctive is the injection-based bypass\r\ndescribed above, which to our knowledge has only been used by Lumma and now also Remus. \r\nAntiVM CPUID checks\r\nAnother identical technique shared by Remus and Lumma is their anti-VM check based on the  cpuid  instruction\r\nwith  EAX  set to  0x40000000 – the hypervisor vendor identification leaf. When executed inside a virtual\r\nmachine, the processor returns the hypervisor’s vendor signature in EBX:ECX:EDX . Both Remus and Lumma\r\nspecifically check the  ECX  portion (4 bytes) against five known hypervisor signatures: KVM ( KVMKVMKVM ),\r\nQEMU/TCG ( TCGTCGTCGTCG ), VMware ( VMwareVMware ), VirtualBox ( VBoxVBoxVBox ), and Xen\r\n( XenVMMXenVMM ). Notably, both check the same substrings of hypervisor names in the same order, and the\r\nchecked substrings are obfuscated. \r\nThe decompiled code performing the check can be seen in Figure 15 (Remus) and Figure 16 (Lumma). For clarity,\r\nwe omitted the decryption loops from the Lumma listing, but they are the same decryption loops we described\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 8 of 19\n\nearlier.\r\nFigure 15: Remus’s AntiVM cpuid checks. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nFigure 16: Lumma’s AntiVM cpuid checks. Reference sample:\r\n8b6b238ffa6e411229c6754ba99f7b990c49edfb2c34068ce0ac5564824d71ad.\r\nCrypter check\r\nWhen executed without a protective layer, both Remus and Lumma display a warning dialog before proceeding\r\nwith their malicious payloads (see Figures 17 and 18). The purpose of this mechanism is twofold: to discourage\r\ndistributors from spreading the raw, unprotected executables, which are more easily detected by security products,\r\nand to prevent less skilled affiliates from accidentally infecting their own machines.\r\nFigure 17: Lumma’s warning dialog triggered when executed without a protective layer.\r\nFigure 18: Remus’s error dialog when executed without a protective layer.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 9 of 19\n\nThis type of check is relatively rare. So far, only a handful of families have been observed employing it – namely,\r\nLumma, Rhadamanthys, Remus, and more recently also AuraStealer, albeit AuraStealer takes a slightly different\r\napproach (rather than simply displaying a warning and waiting for the user to click Yes or OK , it additionally\r\nrequires the user to enter a randomly generated code shown in the dialog). \r\nHowever, despite the surface-level similarity, the underlying implementations differ. Rhadamanthys displays its\r\nwarning dialog using MessageBoxW , whereas both Remus and Lumma invoke it through a direct\r\nNtRaiseHardError syscall. \r\nDirect syscalls/sysenters\r\nBoth Remus and Lumma make use of direct syscalls/sysenters. While their implementations differ in some details,\r\nmostly as a natural consequence of the 32-bit vs 64-bit architecture, the overall design is strikingly similar.\r\nOne of the first things both do upon execution is that they enumerate all Nt -prefixed exports from ntdll.dll\r\nand build a lookup table mapping their name hashes to the corresponding Syscall Service Numbers (SSNs). The\r\nprocess is the same in both cases: they walk the ntdll.dll export directory, hash each matching export name,\r\nand scan the first 32 bytes of each function’s prologue to extract the SSN from the mov eax, \u003cSSN\u003e instruction.\r\nThe extracted SSN is then stored alongside the export’s name hash in a hash-to-SSN lookup table. \r\nFigure 19: Beginning of the hash-to-SSN table constructed at runtime (built by both Remus and Lumma).\r\nTo invoke a specific syscall/sysenter, both perform a linear scan of the lookup table until they find an entry whose\r\nhash matches the requested function. The SSN from the matching entry is then passed to a central dispatcher,\r\nwhich we refer to as lumma_sysenter and remus_syscall . The calling conventions of those dispatcher\r\nfunctions are nearly identical: the first argument is the SSN, followed by the argument count and the variadic\r\narguments themselves. The only difference is that Lumma expresses the argument count in bytes\r\n( number_of_arguments × 4 ), whereas Remus passes the actual count directly.\r\nFigure 20: Example of Remus’s syscall invocation. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nFigure 21: Example of Lumma’s sysenter invocation. Reference sample:\r\n8b6b238ffa6e411229c6754ba99f7b990c49edfb2c34068ce0ac5564824d71ad.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 10 of 19\n\nThe dispatchers themselves look different at first glance, but this is mostly a consequence of ABI differences\r\nbetween 32-bit and 64-bit code. The core logic is the same: arrange the syscall/sysenter arguments and invoke the\r\nkernel. Remus does so directly via the  syscall  instruction. Lumma, on the other hand, resolves a dispatcher\r\naddress during the hash-to-SSN initialization through a fallback chain: it first attempts to resolve the\r\nWow64Transition export from ntdll.dll using API-hashing, then tries the TEB-\u003eWOW32Reserved , and finally,\r\nif that value is NULL , falls back to a hardcoded sysenter stub. Each of these fallback mechanisms ultimately\r\nachieves the same outcome – a transition from user-mode 32-bit code into the kernel.\r\nFigure 22: Remus’s syscall dispatcher. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nFigure 23: Lumma’s sysenter dispatcher. Reference sample:\r\n8b6b238ffa6e411229c6754ba99f7b990c49edfb2c34068ce0ac5564824d71ad.\r\nSeparately from the 32-bit sysenter dispatch, Lumma also employs Heaven’s Gate, a technique that allows 64-\r\nbit code to be executed from within a 32-bit process on a 64-bit operating system. Lumma uses it specifically\r\nwhen interacting with 64-bit browsers (during the ABE bypass) to call native 64-bit ntdll.dll functions that\r\nhave no NtWow64 equivalents – namely NtCreateThreadEx to start the injected thread, NtFreeVirtualMemory\r\nfor clean up, and NtQueryVirtualMemory to query the browser’s memory layout. The remaining cross-process\r\noperations, such as memory reads, memory allocations, and shellcode writes, are handled through standard\r\nNtWow64 sysenter calls ( NtWow64ReadVirtualMemory64 , NtWow64AllocateVirtualMemory64 , and\r\nNtWow64WriteVirtualMemory64 ). Since Heaven’s Gate is inherently a WoW64 technique applicable only to 32-bit\r\nprocesses running on 64-bit systems, and Remus is a 64-bit binary, it has no equivalent. Remus simply uses direct\r\nsyscalls for all these operations.\r\nShared code patterns\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 11 of 19\n\nAnother striking similarity is the layout of many functions, which shows a remarkable resemblance and, in some\r\ncases, is nearly identical (if we disregard the obfuscation and differences caused by 32-bit vs 64-bit code). While\r\nthere are many such functions, we highlight two concrete examples. The first is the heap allocation wrapper,\r\nwhich Remus and Lumma implement in a virtually identical manner, as illustrated in Figures 24 and 25.\r\nFurthermore, the same holds for other memory helpers, such as the reallocation and deallocation wrappers. \r\nFigure 24: Remus’s heap allocation wrapper function. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nFigure 25: Lumma’s heap allocation wrapper function. Reference sample:\r\n8b6b238ffa6e411229c6754ba99f7b990c49edfb2c34068ce0ac5564824d71ad.\r\nThe second example is the clipboard-stealing routine (Figures 26 and 27). Both implementations follow the same\r\nsequence: they open the clipboard, retrieve its contents, convert it from UTF-16 to UTF-8, decrypt the output\r\nfilename Clipboard.txt using a stack-based decryption loop, and finally append the result to the exfiltration\r\narchive. The only difference is that Remus wraps each API call in a thin stub, which, however, might just be a\r\nresult of different compiler optimization.\r\nFigure 26: Remus’s clipboard stealing function. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 12 of 19\n\nFigure 27: Lumma’s clipboard stealing function. Reference sample:\r\n8b6b238ffa6e411229c6754ba99f7b990c49edfb2c34068ce0ac5564824d71ad.\r\nNotably, although the vast majority of API calls are obfuscated through API hashing, the clipboard functions\r\nOpenClipboard , GetClipboardData , and CloseClipboard are among the very few unobfuscated imports in\r\nboth binaries.\r\nEqually notable is the entry point structure. Both Remus and Lumma begin executing their malicious logic\r\ndirectly from the PE entry point (the  start  function), with no runtime initialization whatsoever. In a typical\r\nC/C++ binary, the entry point calls the CRT startup routine, which sets up the heap, initializes global variables,\r\nregisters exception handlers, processes the command line, and invokes static constructors before eventually\r\ncalling  main . However, neither Remus nor Lumma follows this pattern. Instead, both jump directly from the\r\nentry point into their core logic: resolving modules and APIs, performing anti-analysis checks, initializing C2\r\ncommunication, and exfiltrating data, ending with a direct call to ExitProcess(0) . This suggests both were\r\ncompiled without linking to the standard C runtime, likely a deliberate choice to minimize the binary size and\r\nreduce unnecessary dependencies.\r\nThe structural similarities extend further. Both binaries share the same section layout, with each section serving an\r\nidentical semantic role. Even the C2 configuration follows the same pattern: in both cases, the encrypted blob,\r\nkey, and nonce are statically embedded in the .rdata section and decrypted at runtime using ChaCha20.\r\nTo summarize, we strongly believe the degree of similarity between the two codebases is too deliberate to be a\r\ncoincidence, pointing to a single, shared origin.\r\nIndirect control flow obfuscation\r\nFinally, both Remus and Lumma make use of control flow obfuscation by replacing direct jumps with indirect\r\nones, where the target is read from an offset stored in the .data section. As shown in Figure 28, this technique\r\ntakes several forms. In its simplest case, the target address is resolved by a single pointer dereference (highlighted\r\nin yellow). In more complex cases, it extends to a full jump table, where a computed index selects among several\r\ntarget addresses stored consecutively in the .data section (highlighted in orange). Lastly, in some cases, the\r\ntarget address is first loaded into a register (highlighted in blue) and then jumped to via a register-based indirect\r\njump (highlighted in pink).\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 13 of 19\n\nFigure 28: Randomly selected disassembly code from Remus (left) and Lumma (right) highlighting the different\r\nforms of the discussed indirect control flow obfuscation.\r\nAlthough functionally equivalent to normal jumps, the indirection complicates the control flow analysis in two\r\nways. First, some of the indirect jump targets cannot be statically determined (for example, when the register\r\nholding the target address can be set in multiple different preceding basic blocks, each potentially loading a\r\ndifferent target), leaving the control flow graph incomplete as the connections between basic blocks are lost.\r\nSecond, some of these orphaned blocks end up being misidentified as standalone functions, further disrupting\r\nfunction boundary detection and leading to a broken, incomplete decompiler output. Notably, Figure 28 also\r\nprovides another glimpse of the nop paddings discussed earlier.\r\nFigure 29: A portion of the .data section in Remus (left) and Lumma (right) showing the consecutively stored\r\ntarget addresses referenced by the indirect jumps and jump tables.\r\nBoth binaries contain hundreds of these indirect dispatch points, and once again, if we set aside the minor\r\ndifferences arising from the 32-bit vs. 64-bit differences, the obfuscation is virtually identical. Although removing\r\nthe obfuscation entirely requires more effort, the IDA’s decompiler output can be significantly improved by\r\nmarking the referenced offsets as constants. For those interested, we covered this trick in our AuraStealer blog\r\npost, along with other approaches for tackling control flow obfuscation.\r\nWhat’s new in Remus\r\nHaving covered the key similarities between Remus and Lumma, let’s now focus on what is new in Remus. The\r\ncore architecture is clearly inherited from Lumma. Still, several changes go beyond a straightforward 32-bit-to-64-\r\nbit port. These range from refactored strings and more thorough device fingerprinting to a different exfiltration\r\narchive format, a switch from FNV-1a to CRC32 for API hashing, and an overhauled C2 communication that has\r\nbeen restructured to deliver dynamic configurations incrementally rather than in a single large blob.  \r\nThe most significant changes, however, are the replacement of traditional Steam/Telegram dead drop resolvers\r\nwith blockchain-based C2 resolution using EtherHiding, a persistence technique allowing attackers to store\r\nmalware-related data (such as domains, payloads, encryption keys, among others) on blockchain as a smart\r\ncontract, and the introduction of additional anti-analysis checks.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 14 of 19\n\nDead drop resolvers via EtherHiding\r\nBoth Remus and Lumma employ dead drop resolvers, a mechanism in which the malware does not contact its C2\r\nserver directly but instead retrieves the C2 address at runtime from an intermediary hosted on a legitimate\r\nplatform. This makes the infrastructure significantly more resilient, as if a C2 domain is taken down, the dead\r\ndrop can be adjusted to point to a new server without the need to distribute an updated binary. Moreover, the dead\r\ndrop URLs typically reside on well-known, high-reputation services, making them more difficult for security\r\nvendors to block without collateral damage.\r\nWhere the two differ is in the choice of platform. Lumma relies on Steam profiles and Telegram channels,\r\ntypically encoding C2 URLs using ROT-15, while Remus goes a step further, replacing these with Ethereum smart\r\ncontracts. At runtime, it sends an eth_call JSON-RPC request to a hardcoded contract address via a public RPC\r\nendpoint and extracts the C2 URL from the hex-encoded response. Because blockchain data is decentralized and\r\nimmutable, there is no platform operator to report abuse to, making the dead-drop effectively immune to\r\ntakedowns.\r\nJust like Lumma’s Steam profile URL, Remus stores the smart contract address separately from the static C2\r\nconfiguration, as a stack-encrypted string. Notably, after the JSON response is retrieved, the C2 address is, at least\r\nfor now, merely hex-encoded in the result field (no other obfuscation, such as ROT-15 in case of Lumma, is\r\napplied).\r\nFigure 30: Remus resolving a C2 using EtherHiding. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69 (Remus).\r\nFigure 31: Remus’s EtherHiding C2-resolution visualization. The Python script is only a helper to illustrate what\r\nhappens under the hood – in practice, Remus sends an HTTP POST request.\r\nAdditional anti-analysis checks\r\nRemus introduces two additional anti-analysis mechanisms, both evaluated early during startup (before connecting\r\nto its C2). If either check fails, the malware silently terminates via ExitProcess(0) . \r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 15 of 19\n\nThe first check targets sandbox and analysis tool DLLs. Remus walks the PEB's InLoadOrderModuleList , hashes\r\nthe name of every loaded module using a CRC32 variant with a custom initialization constant, and compares the\r\nresult against 11 pre-computed hashes stored in its .rdata section, each corresponding to a module commonly\r\ninjected by analysis environments, including Avast sandbox ( snxhk.dll ), Sandboxie ( sbiedll.dll ), Comodo\r\nsandbox ( cmdvrt32.dll , cmdvrt64.dll ), and several others. \r\nFigure 32: Remus’s CRC32-hashed array of forbidden DLLs. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nFigure 33: Remus’s anti-analysis check for forbidden DLLs. Reference sample:\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69.\r\nIf no sandbox DLLs are detected, Remus proceeds by expanding the path  %UserProfile%\\Documents\\Outlook\r\nFiles , enumerating all  *.pst  files in that directory, and checking for the presence of  honey@pot.com.pst ,\r\nwhich would be treated as a sandbox environment. \r\nSummary\r\nIn this analysis, we described our findings regarding a new x64 variant of Lumma Stealer, which we call Remus.\r\nWe focused on various similarities and differences between Lumma and Remus, thoroughly describing the\r\ntechnical aspects of both. We also provided a timeline, referencing the very first test builds of Remus (historically\r\ncalled Tenzor), which correlate with the extensive doxxing of Lumma authors from late August to October 2025.\r\nThe key aspects that allowed us to attribute Remus as a new x64 version of Lumma are the use of the same string\r\nobfuscation technique, AntiVM checks, direct syscall/sysenter handling, indirect control flow obfuscation, and,\r\nmost importantly, an almost identical approach to bypassing AppBound Encryption, which we’ve only seen used\r\nby Lumma to date.\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 16 of 19\n\nRemus, however, is not merely an x64 port of Lumma. It also introduces a couple of novel techniques in how it\r\noperates, including the use of EtherHiding instead of a traditional approach of using Steam/Telegram as dead drop\r\nresolvers, as well as additional anti-analysis checks to evade a wider range of security vendors.\r\nIndicators of Compromise (IoCs)\r\nA complete IoC list is available on our GitHub.\r\nC2 domains\r\nhxxp[://]217[.]156[.]122[.]12:80\r\nhxxp[://]217[.]156[.]122[.]57:80\r\nhxxp[://]217[.]156[.]122[.]75:1378\r\nhxxp[://]45[.]151[.]106[.]110:80\r\nhxxp[://]80[.]97[.]160[.]155:80\r\nhxxp[://]86[.]107[.]168[.]103:80\r\nhxxp[://]94[.]231[.]205[.]229:28313\r\nhxxp[://]adveryx[.]biz:6573\r\nhxxp[://]backbou[.]biz:5902\r\nhxxp[://]baxe[.]pics\r\nhxxp[://]baxe[.]pics:48261\r\nhxxp[://]borscer[.]biz:9592\r\nhxxp[://]buccstanor[.]pics\r\nhxxp[://]buccstanor[.]pics:28313\r\nhxxp[://]buccstanor[.]pics:48261\r\nhxxp[://]chalx[.]live:5902\r\nhxxp[://]chromap[.]biz:4219\r\nhxxp[://]coox[.]live:28313\r\nhxxp[://]drymoge[.]biz:4192\r\nhxxp[://]forestoaker[.]com:6290\r\nhxxp[://]gluckcreek[.]online:48261\r\nhxxp[://]intem[.]lat:9592\r\nhxxp[://]interxo[.]biz:7481\r\nhxxp[://]josegza[.]biz:8521\r\nhxxp[://]krondez[.]com:28982\r\nhxxp[://]lazzo[.]bet:3989\r\nhxxp[://]managew[.]biz:5902\r\nhxxp[://]navelum[.]biz:3201\r\nhxxp[://]nitroca[.]biz:6782\r\nhxxp[://]outcrol[.]biz:4895\r\nhxxp[://]padaz[.]pics:4219\r\nhxxp[://]parky[.]pics:3989\r\nhxxp[://]prickaz[.]biz:2039\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 17 of 19\n\nhxxp[://]remnane[.]biz:5692\r\nhxxp[://]ropea[.]top:28313\r\nhxxp[://]siltsoh[.]biz:7481\r\nhxxp[://]texakgi[.]cloud:3849\r\nhxxp[://]vinte[.]online\r\nhxxp[://]vinte[.]online:28313\r\nhxxp[://]woodena[.]biz:7821\r\nhxxp[://]zadno[.]run:4219\r\nhxxps[://]cheekiez[.]biz\r\nhxxps[://]nobleckly[.]biz\r\nRemus SHA-256 (Non-exhaustive)\r\n0a8f734f10400f7ae8fef591147e78dab6350089683be84c1cb6c82113cb1319\r\n25e74a76f2f3601abcb20fd743a7e3cf3befd5a3838c7501af5d87d293233809\r\n4428c3ffe2532f162f31d7573bbc1cca2299195421da3d8e8a3e535e9fc42b08\r\n484e3ab5d425a97819f01dcc330e005dc444c51625bfdcd7ea9a3954018d1fc9\r\n64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69\r\n788b56e9be2f1dd6a977dce0265f293ab42d3e8ffb287ab584e169fbf115da1f\r\n8653d7158486aa10fc0078c3ca9318cd7ace05d4b3e6f3b1fb84ffb7a6a339ec\r\na4f111e5425690fcd384c62ecb5b57b0f645925572af3541748e01d810cd2b40\r\nab2e47720388fa201e242552f8d8b82363c6c52f6c63fa3fec9dce027cb12e77\r\nbc11d036fe59abb3915f736307c56d2fd43e8127e46c31f926eeda864f4d66dc\r\nc3f7cea80dbafaa90a88b28a6dfb1227caaf5c2a29f0ce06bf663d6ed2cfc079\r\nTenzor SHA-256 \r\n0580ebf601989457f0708799b431fd4d9f5e59d98838282d72936099aa6636da\r\n7a25253e6d8d9ccf62a67f8014cacb301daf9e40f1b68ecc7f354d6896d16960\r\ncab7855ccfca19a06eea76e0e170f592dcc95906ecfa5436f5a11947e04e63d5\r\ndfbeab30d14df9104a95de83ab4690308c653eb0c3706554687c45a77adc1385\r\nVojtěch Krejsa\r\nThreat Researcher at Gen\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 18 of 19\n\nJan Rubín\r\nThreat Research Team Lead\r\nSource: https://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nhttps://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer\r\nPage 19 of 19",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.gendigital.com/blog/insights/research/remus-64bit-variant-of-lumma-stealer"
	],
	"report_names": [
		"remus-64bit-variant-of-lumma-stealer"
	],
	"threat_actors": [],
	"ts_created_at": 1775791245,
	"ts_updated_at": 1775791338,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f5d3d9bd646ba2901eaea60862d5e93fd471c367.pdf",
		"text": "https://archive.orkl.eu/f5d3d9bd646ba2901eaea60862d5e93fd471c367.txt",
		"img": "https://archive.orkl.eu/f5d3d9bd646ba2901eaea60862d5e93fd471c367.jpg"
	}
}