<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Malware Internals]]></title><description><![CDATA[MalwareInternals is a technical blog dedicated to malware analysis, reverse engineering, and low-level system internals. Its purpose is to explore how malicious software operates and to document techniques used to analyze and understand modern threats.

Cybersecurity is a collective effort. Sharing knowledge and collaborating with the wider security community is essential to better understand emerging threats and strengthen our defenses against cybercrime. This blog aims to contribute to that shared knowledge by documenting research, analysis, and practical insights.]]></description><link>https://malwareinternals.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 29 Apr 2026 15:14:59 GMT</lastBuildDate><atom:link href="https://malwareinternals.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Analyzing LummaC2 stealer’s novel Anti-Sandbox technique: Leveraging trigonometry for human behavior detection]]></title><description><![CDATA[The Malware-as-a-Service (MaaS) model, and its readily available scheme, remains to be the preferred method for emerging threat actors to carry out complex and lucrative cyberattacks. Information thef]]></description><link>https://malwareinternals.com/lummac2-anti-sandbox-technique-trigonometry-human-detection</link><guid isPermaLink="true">https://malwareinternals.com/lummac2-anti-sandbox-technique-trigonometry-human-detection</guid><category><![CDATA[lummac2]]></category><category><![CDATA[Lumma stealer]]></category><category><![CDATA[reverse engineering]]></category><category><![CDATA[Malware]]></category><dc:creator><![CDATA[Alberto Marín]]></dc:creator><pubDate>Sat, 14 Mar 2026 22:30:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/7a903a5e-8857-45b5-b402-52a134f6d530.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Malware-as-a-Service (MaaS) model, and its readily available scheme, remains to be the preferred method for emerging threat actors to carry out complex and lucrative cyberattacks. Information theft is a significant focus within the realm of MaaS, with a specialization in the acquisition and exfiltration of sensitive information from compromised devices, including login credentials, credit card details, and other valuable information. This illicit activity represents a considerable threat that can lead to substantial financial losses for both organizations and individuals.</p>
<p>In this post, we’ll take a deep dive into a new <strong>Anti-Sandbox</strong> technique that <a href="https://malwareinternals.com/lummac2-stealer-everything-you-need-to-know">LummaC2 v4.0 stealer</a> is using to avoid detonation if no human mouse activity is detected. To be able to reproduce the analysis, we will also assess the <strong>packer</strong> and LummaC2 v4.0 new <strong>Control Flow Flattening</strong> obfuscation (present in all samples by default) to effectively analyze the malware. Analysis of the packer is also relevant, as the threat actor selling LummaC2 v4.0 strongly discourages spreading the malware in its unaltered form.</p>
<h2>LummaC2 v4.0 updates</h2>
<p><strong>LummaC2</strong> is an information stealer written in C language sold in underground forums since December 2022. You can find an <a href="https://malwareinternals.com/lummac2-stealer-everything-you-need-to-know">in-depth analysis of the malware</a> assessing LummaC2’s primary workflow, its different obfuscation techniques, and how to overcome them to effectively analyze the malware with ease. The malware has since gone through different updates and is currently on version <strong>4.0</strong>.</p>
<p>Some of these significant updates include:</p>
<ul>
<li><p><a href="https://malwareinternals.com/lummac2-anti-sandbox-technique-trigonometry-human-detection#control-flow-flattening"><strong>Control Flow Flattening</strong></a> obfuscation implemented in default builds (even without using a packer).</p>
</li>
<li><p>New <a href="https://malwareinternals.com/lummac2-anti-sandbox-technique-trigonometry-human-detection#new-anti-sandbox-technique-using-trigonometry-to-detect-human-behavior"><strong>Anti-Sandbox</strong></a> technique to <strong>delay</strong> detonation of the sample until human mouse activity is detected.</p>
</li>
<li><p><strong>Strings</strong> are now XOR encrypted instead of simply modified by adding junk strings in the middle.</p>
</li>
<li><p>Supports <strong>dynamic configuration</strong> files retrieved from the C2. The configuration is Base64 encoded and XORed with the first 32 bytes of the configuration file.</p>
</li>
<li><p>Enforces threat actors to use a <a href="https://malwareinternals.com/lummac2-anti-sandbox-technique-trigonometry-human-detection#forcing-threat-actors-to-use-a-crypter-for-their-builds"><strong>crypter</strong></a> for their builds.</p>
</li>
</ul>
<p>Some of these functionalities have been covered in <a href="https://www.esentire.com/blog/the-case-of-lummac2-v4-0">recent publications</a>.</p>
<h2>Packer</h2>
<p>The malware that is going to be analyzed during these lines comes from the sample <em>b14ddf64ace0b5f0d7452be28d07355c1c6865710dbed84938e2af48ccaa46cf</em>. The initial component within the sample is the <strong>Packer</strong>, which serves as the outer layer of LummaC2 v4.0. Its primary function is to <strong>obfuscate</strong> the malicious payload and facilitate its execution during runtime without the need for spawning additional processes in the system (using <em>CreateThread</em> instead). The packer’s architecture consists of two distinct layers.</p>
<p>We will now delve into the steps the malware takes to execute the payload through these layers, as well as the various techniques it employs to hinder and slow down the analysis process.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/6165b6d8-d4d9-4c91-b73c-8bc0a49a9cdc.png" alt="Malware Internals - Figure 1. b14ddf64ace0b5f0d7452be28d07355c1c6865710dbed84938e2af48ccaa46cf Packer layers" />

<i>Figure 1. b14ddf64ace0b5f0d7452be28d07355c1c6865710dbed84938e2af48ccaa46cf Packer layers</i>

<h3>Layer 1</h3>
<p>The first layer uses a lot of assembly junk instructions, they differ between packed samples but do not execute any relevant action. Then it will use obfuscation techniques like <em>push+re</em>t and <em>jz+jnz</em> to break disassembly and complicate the analysis, as it can be seen in the following figure:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/d25f6aa0-0827-4e89-850f-00810e9c79f0.png" alt="Malware Internals - Figure 2. Example of push+ret and jz+jnz obfuscation techniques present in Layer 1 of the Packer" />

<i>Figure 2. Example of push+ret and jz+jnz obfuscation techniques present in Layer 1 of the Packer</i>

<p>The malware then executes a lot of junk instructions that do not alter the functionality of the program and proceeds to enter in a big and useless loop; which could be considered as an “<strong>anti-emulation”</strong> technique. It also checks that the value calculated at the end of the loop is the one expected to ensure the loop is actually performed. It then continues by creating a Mutex to ensure no more copies of the malware run at the same time in the infected machine. The <strong>Mutex</strong> name varies between samples.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/f441a1c1-908f-4520-9466-78c061e995bf.png" alt="Malware Internals - Figure 3. Example of useless code present to difficult analysis, and Mutex creation present in Layer 1 of the Packer" />

<i>Figure 3. Example of useless code present to difficult analysis, and Mutex creation present in Layer 1 of the Packer</i>

<p>After this initial logic, the packer starts by decrypting a 15 characters API from kernel32.dll. The string is encrypted and built in the stack byte per byte. The API being resolved is “<em>VirtualProtect</em>” used to give <em>PAGE_EXECUTE_READWRITE</em> protections to a fixed address that will contain the <strong>second layer</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/af712f8d-11a7-4dd4-8732-38f3a53e2ce9.png" alt="Malware Internals - Figure 4. Decryption of “VirtualProtect” to resolve Windows API without exposing clear string" />

<i>Figure 4. Decryption of “VirtualProtect” to resolve Windows API without exposing clear string</i>

<p>After <em>VirtualProtect</em> is successfully executed, it proceeds to decrypt the second stage with fixed hardcoded size of <strong>6556 bytes</strong>. Once the layer has been decrypted, execution is transferred via <em>call</em> instruction with the fixed address of the <strong>second layer</strong>. The following picture shows the decryption algorithm for the <strong>next</strong> layer:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/5557018d-7f68-4bd8-9ecf-033ba28ee9cf.png" alt="Malware Internals - Figure 5. Decryption algorithm for the second Layer of the Packer" />

<i>Figure 5. Decryption algorithm for the second Layer of the Packer</i>

<h3>Layer 2</h3>
<p>This layer is very similar to the first one as it uses the same obfuscation techniques to break disassembly and slow down analysis. However, this second stage will eventually extract, decrypt and execute LummaC2 v4.0. This is accomplished by loading a resource using <em>LoadResource</em> and <em>LockResource</em>. The <strong>Resource Name</strong> is hardcoded as “<strong>3</strong>” and contains the final stage (encrypted).</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/674eeb06-09e5-478d-82d2-3d16bd398e85.png" alt="Malware Internals - Figure 6. Last Layer is contained in the packed PE resources" />

<i>Figure 6. Last Layer is contained in the packed PE resources</i>

<p>The resource is decrypted using a very similar algorithm from the first layer, constants change but the <strong>algorithm remains the same</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/f597d60f-f785-4b63-95e1-023bad123b9b.png" alt="Malware Internals - Figure 7. Decryption algorithm for last Layer (LummaC2 v4.0)" />

<i>Figure 7. Decryption algorithm for last Layer (LummaC2 v4.0)</i>

<p>Once the resource has been decrypted, the packer will check if this resource is a PE file, (it checks if “PE” magic is present at offset 0x3C from the base address of the decrypted resource). If the check fails, the program finishes abruptly. This shows how the packer is only expecting a PE in its resources.</p>
<p>A copy of the decrypted resource is then made into an allocated buffer. At this point, we can safely <strong>dump</strong> the unpacked memory just after it has been written decrypted in the allocated buffer (and before relocations are made). We will have now the unpacked LummaC2 v4.0 stealer, which we can dump for further analysis.</p>
<p>The packer finally loads this new PE in its process virtual address space by copying the payload and applying relocations. Finally, it will execute its <strong>Original EntryPoint</strong> via <em>CreateThread</em> using <em>NTHeaders-&gt;OptionalHeader.AddressOfEntryPoint</em> as the <em>ThreadRoutine</em> parameter.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/c9a329cc-7812-4101-9694-321564f27a20.png" alt="Malware Internals - Figure 8. Entry Point of LummaC2 v4.0 is executed via CreateThread" />

<i>Figure 8. Entry Point of LummaC2 v4.0 is executed via CreateThread</i>

<p>This will transfer execution to the <strong>unpacked</strong> LummaC2 v4.0. The following figure shows the <strong>main</strong> routine of the malware:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/35a48d8a-35b3-40f8-9da2-268017779cab.png" alt="Malware Internals - Figure 9. LummaC2 v4.0 main routine" />

<i>Figure 9. LummaC2 v4.0 main routine</i>

<h2>Control Flow Flattening</h2>
<p>Control Flow Flattening is an obfuscation technique aimed at breaking the original flow of the program and complicating its analysis. Furthermore, it makes use of <strong>opaque predicates</strong> and <strong>dead code</strong> to complicate analysis and make identification of relevant blocks more difficult.</p>
<p><strong>Opaque predicates</strong> are employed to introduce intricacy into the primary program logic. Typically, this is achieved by creating additional branches within the control flow, often through the utilization of conditional jumps. Even though these conditional jumps are present, the overall behavior of the program remains deterministic.</p>
<p><strong>Dead code</strong> refers to parts of the malicious code that are inactive or unreachable during the execution of the malware. Dead code may include obsolete functions, unused variables, or entire blocks of instructions that do not contribute to the malware’s intended functionality. In LummaC2 v4.0 some dead code blocks can be recognized by the use of calls to other identified routines of the analyzed malware with invalid parameters, for instance.</p>
<p>The following picture represents the control flow of a program before and after Control Flow Flattening has been applied. As can be seen, this obfuscation technique is used to break the flow of a program by <strong>flattening</strong> it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/e03618a5-c4b2-4062-b2ef-d92644440341.png" alt="Malware Internals - Figure 10. Example of program flow after applying Control Flow Flattening obfuscation" />

<i>Figure 10. Example of program flow after applying Control Flow Flattening obfuscation</i>

<p>This detailed depiction of <a href="https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html">Qarkslab</a> provides a more in-depth exploration of the distinct elements within a control flow graph that has been subjected to Control Flow Flattening obfuscation.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/9a0c64ba-e0e0-47f0-9ab5-57e2cb0b6005.png" alt="Malware Internals - Figure 11. Schematic representation of the different elements in a control flow graph subjected to CFF" />

<i>Figure 11. Schematic representation of the different elements in a control flow graph subjected to CFF</i>

<p>When dealing with this form of obfuscation, the initial step involves identifying the <strong>main dispatcher</strong>, the <strong>relevant blocks</strong>, and the <strong>predispatcher</strong>.</p>
<p>The <strong>main dispatcher</strong> represents the program block where execution always returns after completing one of its branches.</p>
<p>The <strong>relevant blocks</strong> encompass those blocks integral to the functional program, as evident in the initial representation, such as Block 1 and Block 2, etc.</p>
<p>The <strong>predispatcher</strong> holds the responsibility of altering the parameter’s value, which the main dispatcher evaluates to determine the redirection of the execution flow toward a specific branch.</p>
<p>We will now delve into the initial routine executed by the unpacked LummaC2 v4.0, which incorporates an <strong>anti-sandboxing</strong> technique that we will examine in more detail shortly.</p>
<h3>Control structure on stack</h3>
<p>Trying to identify relevant blocks is not easy when CFF has been applied. We are going to analyze how this routine manages its control flow in order to make analysis easier. LummaC2 v4.0 will mostly store the value needed for the control flow dispatchers (sometimes more than one value is used to redirect the flow of the program) either in a local variable or at an offset to a memory location pointed to by a register.</p>
<p>The following picture shows the first instructions executed in the routine, (which is the responsible for executing anti-sandbox protection), the “prologue”. As can be seen, an <strong>extra stack space</strong> of <strong>0xD8 bytes</strong> is reserved and assigned to <strong>ESI</strong> register. This register will be used throughout the entire routine accessing at offsets relative to it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/d39bdf36-cd54-4d1d-ad0d-d6c901c5d790.png" alt="Malware Internals - Figure 12. LummaC2 v4.0 assigning extra stack space to ESI. Other routines use alloca_aprobe instead" />

<i>Figure 12. LummaC2 v4.0 assigning extra stack space to ESI. Other routines use alloca_aprobe instead</i>

<p>To ease analysis, we can create a <strong>Structure</strong> that will hold a maximum of the extra stack space allocated and fill its values while we are reverse engineering the sample.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/062f4ad6-4ea1-4d40-8cc7-63fa813db25a.png" alt="Malware Internals - Figure 13. After applying structure type, we can see the angle of 45.0 is assigned to a member of the structure" />

<i>Figure 13. After applying structure type, we can see the angle of 45.0 is assigned to a member of the structure</i>

<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/2aeae23a-e4c6-4618-ae15-8775f93d963e.png" alt="Malware Internals - Figure 14. Naming structure fields while reverse engineering will help determine the routine’s functionality" />

<i>Figure 14. Naming structure fields while reverse engineering will help determine the routine’s functionality</i>

<h3>Relevant blocks</h3>
<p>Unit42 researchers <a href="https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-4/">published an IDA Python script</a> to help reverse engineering complex code. The following code is a slightly modified version of the one presented so that it runs in IDA Python 7.4+ and allows to execute in a running debugging session providing an <em>end_address</em>.</p>
<pre><code class="language-python">def init_heads(start_addr=None, end_addr=None): 
    if not start_addr or not end_addr: 
        start_addr = idc.get_segm_start(idc.get_screen_ea()) 
        end_addr = idc.get_segm_end(idc.get_screen_ea()) 
 
    heads = Heads(start_addr, end_addr) 
  
    for i in heads: 
        idc.set_color(i, CIC_ITEM, 0xFFFFFF) 
  
  
def get_new_color(current_color): 
    colors = [0xffe699, 0xffcc33, 0xe6ac00, 0xb38600] 
    if current_color == 0xFFFFFF: 
        return colors[0] 
    if current_color in colors: 
        pos = colors.index(current_color) 
    if pos == len(colors)-1: 
        return colors[pos] 
    else: 
        return colors[pos+1] 
    return 0xFFFFFF # Default color 
  
 
def main_debug(end_addr): 
    if not end_addr: 
        print("[!] Error, end_addr for main_debug not specified.") 
        return 
 
    idc.enable_tracing(TRACE_STEP, 1) 
    event = ida_dbg.wait_for_next_event(WFNE_ANY|WFNE_CONT, -1) # Continue the debugger, now configured for step tracing 
 
    while True: 
        event = ida_dbg.wait_for_next_event(WFNE_ANY, -1) 
        addr = idc.get_event_ea() 
 
        if end_addr: 
            if addr == end_addr: 
                print("[.] End address reached.") 
                break 
 
        current_color = idc.get_color(addr, CIC_ITEM) 
        new_color = get_new_color(current_color) 
 
        idc.set_color(addr, CIC_ITEM, new_color) 
 
        if event &lt;= 1: 
            print("[.] Finished debugging.") 
            break 
 
    ida_dbg.suspend_process() # Wait for debugger to reach this point. PROGRAM FINISHED after this 
  
 
if __name__ == '__main__': 
    start_addr = None 
    end_addr = 0x011D21B8 
    init_heads(start_addr, end_addr)    # Set white color (default) 
    main_debug(end_addr) 
</code></pre>
<p>With this script, we can (in a debugging session) select an <em>end_address</em> and let the debugger execute until this address is reached (or the debugging session is finished by other means). As the debugger runs, it will <strong>color</strong> the instructions it executes. The darker the instruction looks, the more times the debugger executed it.</p>
<p>As a result, we can easily identify those code blocks that got executed multiple times (they may be pre-dispatcher blocks), those that got executed only once and even detect <strong>dead code</strong> blocks that never get executed. This can significantly improve the reversing experience of the malware sample, especially if we can avoid looking at dead code.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/dde7bfb9-8c9d-46c0-aea6-a6ee2099b648.png" alt="Malware Internals - Figure 15. Routine with CFF obfuscation before running IDAPython script" />

<i>Figure 15. Routine with CFF obfuscation before running IDAPython script</i>

<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/595d88f1-3897-4baf-8db0-8744a49861ff.png" alt="Malware Internals - Figure 16. Routine with CFF obfuscation after running IDAPython script" />

<i>Figure 16. Routine with CFF obfuscation after running IDAPython script</i>

<h2>New Anti-Sandbox technique: Using trigonometry to detect human behavior</h2>
<p>LummaC2 v4.0 makes use of a <strong>novel anti-sandbox</strong> technique that forces the malware to wait until “human” behavior is detected in the infected machine. This technique takes into consideration different <strong>positions of the cursor</strong> in a short interval to detect human activity, <strong>effectively preventing detonation</strong> in most analysis systems that do not emulate mouse movements realistically.</p>
<p>The malware first starts by getting the <strong>initial position</strong> of the cursor with <em>GetCursorPos()</em> and waits in a loop for <strong>300</strong> milliseconds until a new capture of the cursor position is different from the initial one.</p>
<p>Once it has detected some mouse movement (as the cursor position has changed) it will save the next <strong>5 positions</strong> of the cursor executing <em>GetCursorPos()</em> with a small <em>Sleep</em> of <strong>50</strong> milliseconds between them.</p>
<p>After these 5 cursor positions have been saved, (let’s name them for now on <strong>P0, P1, …, P4</strong>), the malware checks if every captured position is different from its preceding one. For instance, it checks if ((P0 != P1) &amp;&amp; (P1!= P2) &amp;&amp; (P2 != P3) &amp;&amp; (P3 != P4)). If this condition is not met, LummaC2 v4.0 will start again capturing a new “initial position” with a <em>Sleep</em> of 300 milliseconds until mouse movement is found and save new 5 cursor positions. This process will repeat <strong>forever</strong> until all consecutive cursor positions <strong>differ</strong>.</p>
<p>At this point we know the malware ensures that detonation will not occur until mouse is moved “quickly”. (Sandbox solutions that emulate mouse with less frequency will never detonate the malware).</p>
<p>After checking that all 5 captured cursor positions meet the requirements, LummaC2 v4.0 uses <strong>trigonometry</strong> to detect “human” behavior. If it does not detect this human-like behavior, it will start the process all over again from the beginning. The following lines explain the logic the malware follows.</p>
<p>Let’s assume here we have our 5 captured cursor positions (with a gap of 50 milliseconds between them) named P0, P1, …, P4.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/a4cc15e9-59ea-4977-aba3-e7dbdc5f236f.png" alt="Malware Internals - Figure 17. Representation of 5 captured mouse positions with a Sleep(50) between them" />

<i>Figure 17. Representation of 5 captured mouse positions with a Sleep(50) between them</i>

<p>LummaC2 is going to treat these coordinates points as Euclidean vectors. As a result, captured mouse positions form <strong>4 different vectors</strong>: P01, P12, P23, P34.</p>
<img src="http://web.archive.org/web/20241231131325im_/https://outpost24.com/wp-content/uploads/2023/11/image18-Unveiling-LummaC2-stealer.png" alt="Malware Internals - Figure 18. Representation of vectors formed by consecutive points from previous captured mouse positions" />

<i>Figure 18. Representation of vectors formed by consecutive points from previous captured mouse positions</i>

<p>LummaC2 v4.0 then calculates the angle that is formed <a href="https://www.cuemath.com/geometry/angle-between-vectors/">between consecutive vectors</a> P01-P12, P12-P23 and P23-P34 sequentially. Here we can see an example of the different angles that are calculated:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/05af95a2-0ce3-430c-b205-2a1d7dd6bffd.png" alt="Malware Internals - Figure 19. Representation of angles between previous vectors being calculated using dot product of vectors" />

<i>Figure 19. Representation of angles between previous vectors being calculated using dot product of vectors</i>

<p>To get the <strong>magnitude</strong> of a vector (shortest distance between two points), the <strong>Euclidean distance</strong> formula is used. To calculate the <strong>angle</strong> between two Euclidean vectors, it makes use of the <strong>dot product of vectors</strong> and transforms the result from radians to degrees. This can be seen in the following figures:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/ac68ae1e-6d56-4890-af8c-2ff059dc0f10.png" alt="Malware Internals - Figure 20. LummaC2 v4.0 uses Euclidean Distance formula to calculate the magnitude of vectors" />

<i>Figure 20. LummaC2 v4.0 uses Euclidean Distance formula to calculate the magnitude of vectors</i>

<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/7eb884ad-da18-4ebb-bc66-ee05fefd66a9.png" alt="Malware Internals - Figure 21. LummaC2 v4.0 uses dot product of vectors to calculate the angle between two vectors" />

<i>Figure 21. LummaC2 v4.0 uses dot product of vectors to calculate the angle between two vectors</i>

<p>The result is finally converted to <strong>degrees</strong> and compared against a threshold of <strong>45.0º</strong> degrees, which is <strong>hardcoded</strong> in the malware sample and initialized at the beginning of the anti-sandbox routine (as it can be seen in previous <em>Figure 13</em>).</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/8283381b-3fba-44c1-b7e6-e5375d14844d.png" alt="Malware Internals - Figure 22. The angle calculated between vectors is converted from radians to degrees" />

<i>Figure 22. The angle calculated between vectors is converted from radians to degrees</i>

<p>If all the calculated angles are lower than <strong>45º</strong>, then LummaC2 v4.0 considers it has detected “human” mouse behavior and continues with its execution. However, if <strong>any</strong> of the calculated angles is <strong>bigger</strong> than 45º, the malware will start the process all over again by ensuring there is mouse movement in a 300-millisecond period and capturing again <strong>5 new cursor positions</strong> to process.</p>
<p>In the following figure we can see how the anti-sandbox check is performed, once obtained the angle between two vectors in <em>angle_between_vectors_degress.</em> The <em>difference_threshold</em> is initialized at the beginning of the routine (which can be seen in <em>Figure 13</em>).</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/c59291c3-4821-4a00-9d2d-a617d4c462c4.png" alt="Malware Internals - Figure 23. The angle calculated between vectors is compared against a hardcoded threshold of 45.0 degrees" />

<i>Figure 23. The angle calculated between vectors is compared against a hardcoded threshold of 45.0 degrees</i>

<h3>How to bypass LummaC2 v4.0 Anti-Sandbox technique</h3>
<p>As we have seen, this technique allows the malware to <strong>delay detonation</strong> until “human” mouse movement is detected. Moreover, this mouse movement must be continuous and smooth. Moving the cursor tortuously, quickly to random positions, circles or changing abruptly the direction of the cursor movement, etc., will most likely result in the malware <strong>never</strong> detonating.</p>
<p>In order to detonate LummaC2 v4.0, mouse movement must be continuous (5 different positions will be captured in approximately 0.25 seconds) and it must be a smooth movement (without changing directions abruptly), given that <strong>angles</strong> between the vectors formed by these positions <strong>cannot exceed 45 degrees</strong>.</p>
<h2>Forcing threat-actors to use a crypter for their builds</h2>
<p>As we saw in earlier LummaC2 advertisements in underground forums, protecting the malware with a <strong>crypter</strong> was recommended to avoid leaking the malware anywhere in its pure form.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/8382b4ca-af7e-4026-bbe6-c17843e76654.png" alt="Malware Internals - Figure 24. Dark Web Post for LummaC2 Stealer. Lines in red show threat actor encouraging to use a crypter" />

<i>Figure 24. Dark Web Post for LummaC2 Stealer. Lines in red show threat actor encouraging to use a crypter</i>

<p><strong>Newer</strong> versions of the malware added a new feature to avoid leaking the unpacked samples. In the case in which the sample being executed is not packed, the infected user will be presented with the following <strong>alert</strong> that allows them to prematurely finish the execution of the malware without <strong>any</strong> <strong>harm</strong> being caused to the infected machine.</p>
<p>This shows the lengths at which the malware developer is willing to go to avoid leaking unpacked samples.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/466279cf-ac88-4f76-98d3-03cee3544cc8.png" alt="Malware Internals - Figure 25. Alert message shown to the user when executing LummaC2 v4.0 in its unpacked form" />

<i>Figure 25. Alert message shown to the user when executing LummaC2 v4.0 in its unpacked form</i>

<p>LummaC2 v4.0 detects if the running executable is <strong>crypted</strong> by searching for a <strong>specific value</strong> at certain <strong>offset</strong> from the PE file. If this value is found, then the sample is not crypted. As we have previously seen, the packer sample does not create a new process to run the malware but uses a thread instead. Analyzing or executing the unpacked sample will result in this message box being displayed.</p>
<p>It will start by executing <em>GetModuleFileNameW</em> with <em>hModule</em> parameter 0 (to retrieve the path of the executable file of the current process) and obtaining a handle to the file for future reading via <em>CreateFileW</em> with <em>dwDesiredAccess</em> parameter set to <em>GENERIC_READ</em>. Then, it will get the size of the file with <em>GetFileSizeEx</em> and allocate a buffer of the obtained size using <em>malloc</em>. Following, a call to <em>ReadFile</em> is executed to fill the buffer with the contents of the executable file of the current process, as shown in the following Figure:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/ce838ca8-f24a-45cf-8f4e-8b03fc255900.png" alt="Malware Internals - Figure 26. LummaC2 v4.0 reading the executable file of the current process to check if the malware is unpacked" />

<i>Figure 26. LummaC2 v4.0 reading the executable file of the current process to check if the malware is unpacked</i>

<p>Afterwards, it will check using <em>memcmp</em> if a <strong>hardcoded mark</strong> is present at a <strong>hardcoded offset</strong> plus 8 bytes. If the contents match, the user is presented with the alert preventing detonation via <em>NtRaiseHardError</em>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/f71d9515-4aa8-4ace-9319-b7b8bf73d0ee.png" alt="Malware Internals - Figure 27. LummaC2 v4.0 checks if the malware is unpacked looking for a hardcoded value at a certain offset" />

<i>Figure 27. LummaC2 v4.0 checks if the malware is unpacked looking for a hardcoded value at a certain offset</i>

<p>This <strong>hardcoded mark</strong> (which changes between samples) has been previously <strong>hashed</strong> to check for file integrity. If this integrity check does not match, the malware ends abruptly. However, this should not be any issue if the unpacking of the malware was successful.</p>
<p>The following picture shows the hardcoded mark and the hardcoded offset. As well as the contents of the <strong>unpacked version</strong> of the malware, which would show the alert upon execution.</p>
<img src="http://web.archive.org/web/20241231131325im_/https://outpost24.com/wp-content/uploads/2023/11/image28-Unveiling-LummaC2-stealer.png" alt="Malware Internals - Figure 28. LummaC2 v4.0 hardcoded mark and offset used to check if the executing malware is unpacked" />

<i>Figure 28. LummaC2 v4.0 hardcoded mark and offset used to check if the executing malware is unpacked</i>

<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/2d58c1a5-8994-4fcc-9414-604c31555f28.png" alt="Malware Internals - Figure 29. LummaC2 v4.0 unpacked sample contains the hardcoded_mark at hardcoded_offset+8" />

<i>Figure 29. LummaC2 v4.0 unpacked sample contains the hardcoded_mark at hardcoded_offset+8</i>

<h2>Conclusion</h2>
<p>Information Stealers such as LummaC2 v4.0 pose significant risks and have the potential to inflict substantial harm on both individuals and organizations, including privacy breaches and the unauthorized exposure of confidential data. As our analysis has revealed, LummaC2 v4.0 appears to be a dynamic malware strain that remains under <strong>active development</strong>, constantly enhancing its codebase with additional features and <strong>improved obfuscation techniques</strong>, along with updates to its control panel. The ongoing usage of this malware in real-world scenarios indicates that it will likely continue to evolve, incorporating more advanced features and security measures in the future. This evolution is further facilitated by an open channel for customers to request bug fixes and propose enhancements.</p>
<h2>IOCs</h2>
<h3>Hashes</h3>
<p>LummaC2 v4.0 (sample 1)</p>
<ul>
<li><p>b14ddf64ace0b5f0d7452be28d07355c1c6865710dbed84938e2af48ccaa46cf (packed)</p>
</li>
<li><p>4408ce79e355f153fa43c05c582d4e264aec435cf5575574cb85dfe888366f86 (unpacked)</p>
</li>
</ul>
<p>LummaC2 v4.0 (sample 2)</p>
<ul>
<li><p>de6c4c3ddb3a3ddbcbea9124f93429bf987dcd8192e0f1b4a826505429b74560 (packed)</p>
</li>
<li><p>976c8df8c33ec7b8c6b5944a5caca5631f1ec9d1d528b8a748fee6aae68814e3 (unpacked)</p>
</li>
</ul>
<h3>C&amp;Cs</h3>
<p>curtainjors[.]fun</p>
<p>gogobad[.]fun</p>
<p>superyupp[.]fun</p>
<h2>References</h2>
<p>“The Case of LummaC2 v4.0”, September, 2023. [Online]. Available: <a href="https://www.esentire.com/blog/the-case-of-lummac2-v4-0">https://www.esentire.com/blog/the-case-of-lummac2-v4-0</a> [Accessed November 05, 2023]</p>
<p>“Everything you need to know about the LummaC2 stealer: leveraging IDA Python and Unicorn to deobfuscate Windows API hashing”, April, 2023. [Online]. Available: <a href="https://malwareinternals.com/lummac2-stealer-everything-you-need-to-know">https://malwareinternals.com/lummac2-stealer-everything-you-need-to-know</a> [Accessed November 05, 2023]</p>
<p>“Using IDAPython to Make Your Life Easier: Part 4”, January, 2016. [Online]. Available: <a href="https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-4">https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-4</a> [Accessed November 05, 2023]</p>
<p>“A cryptor, a stealer and a banking trojan”, September, 2023. [Online]. Available: <a href="https://securelist.com/crimeware-report-asmcrypt-loader-lumma-stealer-zanubis-banker/110512/">https://securelist.com/crimeware-report-asmcrypt-loader-lumma-stealer-zanubis-banker/110512/</a> [Accessed November 05, 2023]</p>
<p>“Deobfuscation: recovering an OLLVM-protected program”, December, 2014. [Online]. Available: <a href="https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html">https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html</a> [Accessed November 05, 2023]</p>
<p>“The Rise of the Lumma Info-Stealer”, September, 2023. [Online]. Available: <a href="https://darktrace.com/blog/the-rise-of-the-lumma-info-stealer">https://darktrace.com/blog/the-rise-of-the-lumma-info-stealer</a> [Accessed November 05, 2023]</p>
]]></content:encoded></item><item><title><![CDATA[LummaC2 stealer: Everything you need to know]]></title><description><![CDATA[There is a huge spike in the popularity and use of information-stealing malware in underground markets. With more stealing capabilities, simplified administration, and ability to remain undetected, st]]></description><link>https://malwareinternals.com/lummac2-stealer-everything-you-need-to-know</link><guid isPermaLink="true">https://malwareinternals.com/lummac2-stealer-everything-you-need-to-know</guid><category><![CDATA[lummac2]]></category><category><![CDATA[Lumma stealer]]></category><category><![CDATA[reverse engineering]]></category><category><![CDATA[Malware]]></category><dc:creator><![CDATA[Alberto Marín]]></dc:creator><pubDate>Sat, 14 Mar 2026 21:31:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/7071d00b-3a07-4632-aa63-8ad6cd28a8cd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There is a huge spike in the popularity and use of information-stealing malware in underground markets. With more stealing capabilities, simplified administration, and ability to remain undetected, stealers are available to anyone with basic computer knowledge. This is evident in the price evolution of different stealer malware families from 2018-2022.</p>
<p>In this blog post, we will take a deep dive into a malware sample classified as LummaC2, an information stealer written in C language that has been sold in underground forums since December 2022. We assess LummaC2’s primary workflow, its different obfuscation techniques (like Windows API hashing and encoded strings) and how to overcome them to effectively analyze the malware with ease. We will also analyze how networking communications with the C2 work and summarize LummaC2’s MITRE Adversarial Tactics, Techniques and Common Knowledge.</p>
<p>This malware family is an evolution of its predecessor LummaC, developed by the same threat actor, and sold since August 2022 on <a href="https://www.accenture.com/us-en/blogs/security/information-stealer-malware-on-dark-web?tlaAppCB">underground forums</a>.</p>
<h2>New stealer for sale: LummaC2</h2>
<p>The information stealer is offered for sale in several underground forums and via the official shop lumma[.]site by the threat actor "Shamel" using the alias “Lumma”, who is also responsible for the sales of the <a href="https://ke-la.com/information-stealers-a-new-landscape/">7.62mm stealer</a>. Advertisements hav been seen in other forums by the alias “LummaStealer”, which is presumably a reseller of the stealer.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/9335baca-3dc3-4940-be15-9ab9b3ce29fb.png" alt="Malware Internals - Figure 1. Dark Web Post for LummaC2 Stealer" />

<i>Figure 1. Dark Web Post for LummaC2 Stealer</i>

<p>As we will see in detail later, this malware targets crypto wallets, browser extensions, two-factor authentication (2FA) and steals sensitive information from the victim’s machine.</p>
<p>LummaC2 is offered at the following prices depending on the features offered:</p>
<ul>
<li><p>Experienced US$250;</p>
</li>
<li><p>Professional US$500;</p>
</li>
<li><p>Corporate account US$1,000.</p>
</li>
</ul>
<p>An earlier version of the website seen in a screenshot on <a href="https://blog.cyble.com/2023/01/06/lummac2-stealer-a-potent-threat-to-crypto-users/">Cyble's article</a> indicates that it was also possible to purchase the stealer and panel source code for a price of US$20,000.</p>
<p>The purchase of the stealer can be processed through the well-known cryptocurrency exchange Coinbase from a wide range of cryptocurrencies to choose from.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/5a4e3032-495b-44e8-8d11-bb95d6693f8d.png" alt="Malware Internals - Figure 2. Screenshot obtained from LummaC2 shop (information automatically translated from Russian to English)." />

<i>Figure 2. Screenshot obtained from LummaC2 shop (information automatically translated from Russian to English).</i>

<h2>Deobfuscating LummaC2</h2>
<h3>LummaC2 Windows API call Obfuscation</h3>
<p><strong>LummaC2</strong> makes use of <strong>API hashing</strong>, which is a common technique seen in malware in order to hide their functionality from tools relying on static information and to obfuscate the code, which makes it harder for an analyst to understand what the malware does.</p>
<p>The following picture shows an example of how Windows API calls are performed:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/e50c0e34-a2bd-4886-8c7e-71546ef05a0e.png" alt="Malware Internals - Figure 3. Example of an obfuscated call to NtClose for LummaC2" />

<i>Figure 3. Example of an obfuscated call to NtClose for LummaC2</i>

<p>The malware executes a function that receives a DLL name string in EDX register(e.g “ntdll.dll”) and an input hash (in ECX register). This function internally resolves kernel32!LoadLibraryA to load the desired .dll (in this case “ntdll.dll”) and proceeds to parse its Export Table. It hashes each export name until it finds one that matches the input hash. This way it is able to resolve any Windows API Call, saving the address found in EAX register as a result. Then a <strong>call eax</strong> instruction will finally execute the desired Windows API call.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/21c3d59e-463e-4630-a346-b887d24bd882.png" alt="Malware Internals - Figure 4. LummaC2 parsing Export Table and hashing with MurmurHash2 to resolve Windows API calls" />

<i>Figure 4. LummaC2 parsing Export Table and hashing with MurmurHash2 to resolve Windows API calls</i>

<p>The hashing algorithm that <strong>LummaC2</strong> uses to resolve Windows API calls is <strong>MurmurHash2</strong> with 32 as seed value.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/d2963f2e-af37-4f19-b8e2-a5eb1fde86b9.png" alt="Malware Internals - Figure 5. Decompiled view (excerpt) of MurmurHash2 routine using 32 as seed value" />

<i>Figure 5. Decompiled view (excerpt) of MurmurHash2 routine using 32 as seed value</i>

<h3>Defeating LummaC2 Windows API call Obfuscation</h3>
<p>The following lines are aimed at removing the call obfuscation scheme for <strong>LummaC2</strong> now that we know how it resolves Windows API calls. The idea is to automatically resolve all Windows API calls used in the code so that we have a better picture of the malware capabilities without the need of debugging and entering in every single path the malware can take to resolve all its possible calls.</p>
<p>To do so, we will generate a dictionary containing all the Windows API calls from a given set of Windows .dll files and their respective <strong>MurmurHash2</strong>. With this dictionary, we can then get every hash sent to the function resolving Windows Api calls and figure out which function is being resolved.</p>
<h4>Preparing Windows API call hash dictionary</h4>
<p>We could try and find a public implementation or <strong>MurmurHash2</strong> but it is possible that the algorithm the malware uses may be altered in the future so that the standard implementation does not work. For this reason, another good approach is to use <a href="https://www.unicorn-engine.org/">Unicorn</a>, as it allows us to emulate the <strong>exact</strong> instructions that the malware executes.</p>
<h4>Unicorn</h4>
<p>The <strong>hashing</strong> routine is a “standalone” routine that we can extract easily from the binary and does not have any calls or jumps to other locations apart from the hashing routine itself. Which means we can run this shellcode in an emulated environment without previous patching to ensure everything is linked properly (with the exception of the last “return” instruction, which we should ignore for the emulation).</p>
<p>In this scenario, the malware hashing algorithm expects to have the string with the Windows API call in <strong>ECX</strong> register and the result hash (which we will read) is finally stored in <strong>EAX</strong> register.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/d908aeee-1a88-4966-8186-c2e774aac3ef.png" alt="Malware Internals - Figure 6. Call graph view for LummaC2 MurmurHash2 implementation" />

<i>Figure 6. Call graph view for LummaC2 MurmurHash2 implementation</i>

<p>As we now have all the data we need to emulate the binary, the last step for this part is to build the emulation environment for our code to run on. To accomplish this, we will use the open-source <a href="https://www.unicorn-engine.org/">Unicorn Engine</a>.</p>
<p>The first thing we want to do is initializing Unicorn for the architecture we want to emulate (x86 architecture), and map some memory to use. Next, we will write our shellcode to our memory space and initialize <strong>ECX</strong> pointing to our <strong>Export Name</strong> string. With all this in place, we are ready to run emulation and read the resulting hash in <strong>EAX</strong> register afterwards.</p>
<p>The following Python function uses Unicorn to emulate the hashing algorithm <strong>LummaC2</strong> uses to resolve Windows API Calls:</p>
<pre><code class="language-python">import unicorn

def emulate_murmurhash2(data, seed=32):
    code = "\x56\x57\x8B\xF9\x8B\xD7\x8D\x4A\x01\x8A\x02\x42\x84\xC0\x75\xF9\x2B\xD1\x8B\xF2\x83\xF6\x20\x83\xFA\x04\x7C\x4D\x53\x8B\xDA\xC1\xEB\x02\x6B\xC3\xFC\x03\xD0\x0F\xB6\x4F\x03\x0F\xB6\x47\x02\xC1\xE1\x08\x0B\xC8\x69\xF6\x95\xE9\xD1\x5B\x0F\xB6\x47\x01\xC1\xE1\x08\x0B\xC8\x0F\xB6\x07\xC1\xE1\x08\x83\xC7\x04\x0B\xC8\x69\xC9\x95\xE9\xD1\x5B\x8B\xC1\xC1\xE8\x18\x33\xC1\x69\xC8\x95\xE9\xD1\x5B\x33\xF1\x83\xEB\x01\x75\xBF\x5B\x83\xEA\x01\x74\x1C\x83\xEA\x01\x74\x0E\x83\xEA\x01\x75\x1D\x0F\xB6\x47\x02\xC1\xE0\x10\x33\xF0\x0F\xB6\x47\x01\xC1\xE0\x08\x33\xF0\x0F\xB6\x07\x33\xC6\x69\xF0\x95\xE9\xD1\x5B\x8B\xC6\xC1\xE8\x0D\x33\xC6\x69\xC8\x95\xE9\xD1\x5B\x5F\x5E\x8B\xC1\xC1\xE8\x0F\x33\xC1"

    CODE_OFFSET = 0x1000000
    mu = unicorn.Uc(unicorn.UC_ARCH_X86, unicorn.UC_MODE_32)
    mu.mem_map(CODE_OFFSET, 4*1024*1024)
    mu.mem_write(CODE_OFFSET, code)

    libname = 0x7000000
    mu.mem_map(libname, 4*1024*1024)
    mu.mem_write(libname, data)
 
    stack_base = 0x00300000
    stack_size = 0x00100000

    mu.mem_map(stack_base, stack_size)
    mu.mem_write(stack_base, b"\x00" * stack_size)
 
    mu.reg_write(unicorn.x86_const.UC_X86_REG_ESP, stack_base + 0x800)
 
    mu.reg_write(unicorn.x86_const.UC_X86_REG_EBP, stack_base + 0x1000)
 
    mu.reg_write(unicorn.x86_const.UC_X86_REG_ECX, libname)

    mu.emu_start(CODE_OFFSET, CODE_OFFSET + len(code))

    result = mu.reg_read(unicorn.x86_const.UC_X86_REG_EAX)

    return result
</code></pre>
<p>We can now easily write a script to walk through files inside a directory where we have Windows .dlls and, for each .dll, parse its Exports and calculate its <strong>MurmurHash2</strong> using the previous function. This could be an example of the implementation using pefile:</p>
<pre><code class="language-python">import pefile

def dump_hash_dlls():
    '''
    This function uses pefile to get the export names from .dlls and apply the hashing
    algorithm to them.
    '''

    dlls_dir = 'dlls/'    # Directory where we have Windows .DLL files

    for (dirpath, dirnames, filenames) in os.walk(dlls_dir):
        for filename in filenames:
            if filename.endswith('.dll'):

                pe = pefile.PE('{}'.format(dlls_dir+filename))

                for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
                    export_name = exp.name

                    if not export_name:
                        # "Ignoring export without name..."
                        continue

                    try:
                        export_hash = emulate_murmurhash2(export_name)
                    except Exception as err:
                        print('Exception occurred while emulating murmurhash2 with export_name: {}. Error: {} '.format(export_name, err))

                        continue
</code></pre>
<p>The results can then be saved as we prefer. In this case, we need a Python dictionary that we can use in <strong>IDA Python</strong> script when analyzing the malware. We can save the result dictionary in a .json file like the following one:</p>
<pre><code class="language-python">{
    "1002323769": "kernel32_MoveFileTransactedA",
    "1002333354": "ntdll_ZwSetTimer",
    "1003390208": "ntdll_memmove_s",
    "1003407985": "advapi32_LsaDelete",
    "1004879971": "ntdll_NtReadOnlyEnlistment",
    "100560003": "kernel32_GetThreadId",
    "1006629348": "kernel32_ReadConsoleOutputW",
    "1007338292": "kernel32_GetProcessPreferredUILanguages",
    "1007695856": "user32_GetClassInfoExW",
    "1008342899": "shell32_SHCreateStdEnumFmtEtc",
    "1008627276": "advapi32_QueryTraceW",
    "1008723271": "ntdll_NtDisableLastKnownGood",
    "1009340263": "advapi32_RegSaveKeyW",
    "1009496315": "kernel32_FlushViewOfFile",
    "1009939290": "shlwapi_PathFindSuffixArrayW",
    "101018728": "ntdll_ZwOpenTransactionManager",
    "1010305366": "user32_SetWindowLongA",
    "1010398495": "shlwapi_PathUnExpandEnvStringsW",
    "101074275": "kernel32_EnumDateFormatsExW",
    "1011639982": "user32_AppendMenuA",
    "1012134009": "user32_CharToOemBuffW",
    "1012436811": "ntdll_NtCreateNamedPipeFile",
    "1013083577": "shell32_ShellExec_RunDLL",
    "1014808818": "ntdll_NtUnmapViewOfSection",
    "1016118817": "ntdll_NtQueryInformationThread",
    "1017169715": "shell32_ILCloneFirst",
    "1017424400": "user32_ReleaseCapture",
(…)
</code></pre>
<h4>Resolving obfuscated Windows API calls</h4>
<p>Now that we have all the possible exports that the malware may use, it is time to create an <strong>IDA Python</strong> script to help us reverse engineer <strong>LummaC2</strong>.</p>
<p>This script is going to be divided in 2 parts. From one side, we are going to resolve every Windows API call (checking the hash set in ECX against our big dictionary) and create an IDA comment staying the final Windows API call being made. In the end, we will execute another script while debugging LummaC2 to patch all these calls.</p>
<p>This will help us to easily understand how the malware operates and its capabilities to ease reverse engineering without the need of debugging and executing every possible path the malware can take.</p>
<p>The first thing to do is to place our .json file (the one with the big Python dictionary storing all the Windows API calls and respective hashes) in our analysis VMs where IDA Python script is going to be executed. Then the script must be able to read and save its contents for further analysis:</p>
<pre><code class="language-python">import json

hashes_dict = {}

def setup(hashes_dict_file):
    global hashes_dict

    try:
        with open(hashes_dict_file, 'rb') as fd:
            hashes_dict = json.load(fd)
    except Exception as err:
        print('Error while readning hashes dict. file: {}'.format(err))
</code></pre>
<p>Now that we have our dictionary ready. Let’s examine the different patterns that are used when resolving a Windows API call. We know that <strong>ECX</strong> register must have the hash, but this can be achieved in the code through different ways:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/e624dbae-cebd-4cb0-acd7-4e2ca998f89b.png" alt="Malware Internals - Figure 7. Example of different scenarios where Windows API call resolution is made" />

<i>Figure 7. Example of different scenarios where Windows API call resolution is made</i>

<p>As we can see, in the end ECX always contains the hash to be resolved. However, the instruction that sets the specified hash can move it into a different register before being in ECX. The last two patterns use <strong>EDI</strong> and <strong>ESI</strong> registers respectively.</p>
<p>With this information, we should be able to go through all <strong>cross references</strong> to the call “<em>ResolveApiByHash</em>”, retrieve the hash being used and resolve the Windows API call using our big hash dictionary. The following Python function implements this. It only expects to receive the address of the call “<em>ResolveApiByHash</em>” as its only argument.</p>
<pre><code class="language-python">import idautils
import idc

def resolve_all_APIs(resolve_ea):

    patches = []

    total_apis_found = 0
    total_apis_resolved = 0

    global hashes_dict

    if resolve_ea is None:
        print('[!] Resolve failed.')
        return

    for ref in idautils.CodeRefsTo(resolve_ea, 1):

        total_apis_found += 1

        curr_ea = ref

        API_hash = 0

        for _ in range(30): # Search maximum the last 30 instructions before the call to ResolveApiByHash

            prev_instruction_ea = idc.PrevHead(curr_ea)
            instruction = idc.GetDisasm(prev_instruction_ea)

            # Possible scenarios
            '''
            .text:0040214B B9 73 10 FF E8                          mov     ecx, 0E8FF1073h
            .text:00402150 E8 7E 61 00 00                          call    ResolveApiByHashWrapper

            or

            .text:004074F6 BF 29 A1 D3 5F                          mov     edi, 5FD3A129h
            .text:004074FB BA C8 4B 42 00                          mov     edx, offset aWininet_dll ; "wininet.dll"
            .text:00407500 8B CF                                           mov     ecx, edi
            .text:00407502 E8 CC 0D 00 00                          call    ResolveApiByHashWrapper

            or

            .text:00407D8C BE 30 E2 95 3D                          mov     esi, 3D95E230h
            .text:00407D91 8B D3                                          mov     edx, ebx
            .text:00407D93 6A 00                                          push    0
            .text:00407D95 8B CE                                          mov     ecx, esi
            .text:00407D97 E8 37 05 00 00                          call    ResolveApiByHashWrapper
            '''

            instruction_cut = instruction.replace(' ', '')

            if 'movecx' in instruction_cut or 'movedi' in instruction_cut or 'movesi' in instruction_cut:

                API_hash = idc.GetOperandValue(prev_instruction_ea, 1)

                if API_hash &lt; 0x10:
                    # Avoid intermediate movs, when the target is in edi (e.g mov ecx, edi)
                    curr_ea = prev_instruction_ea
                    continue

                API_hash_idx = str(API_hash)

                if API_hash_idx in hashes_dict:

                    print('API hash: {} {} {}'.format(hex(prev_instruction_ea), hex(API_hash), hashes_dict[API_hash_idx]))

                    apicall = hashes_dict[API_hash_idx].split('_')[-1] # "kernel32_AddAtomA" -&gt; "AddAtomA"
                    idc.MakeComm(ref, apicall)

                    patch_info = (ref, hashes_dict[API_hash_idx])
                    patches.append(patch_info)

                    total_apis_resolved += 1

                else:

                    print("Hash not found!")

                break

            curr_ea = prev_instruction_ea

    print('Total APIs found: {} Total APIs resolved: {}'.format(total_apis_found, total_apis_resolved))

    return patches


setup("c:\murmurhash2_hashes_dict.json") # Big Python dict. With hashes and export names
patches = resolve_all_APIs(0x004082D3) # Use address of “ResolveApiByHash”
</code></pre>
<p>The return value is a list of tuples (addr, apicall) that we will use later to <strong>patch</strong> the binary. After executing the script, we can see how now we have comments for every Windows API call resolution and have a better understanding of what the malware can do. We can also use <strong>xref</strong> view to quickly see all the Windows API calls (with their resolved name as a comment) the malware can use.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/cb8fa111-db70-4d1c-bcc5-91f6697f4ae7.png" alt="Malware Internals - Figure 8. Example of result from executing the previous script. Windows API calls are commented now" />

<i>Figure 8. Example of result from executing the previous script. Windows API calls are commented now</i>

<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/de825930-69b2-4ef9-9590-a885f858cd04.png" alt="Malware Internals - Figure 9. Cross-Referencing the Windows API resolution routine can help identifying malware capabilities now" />

<i>Figure 9. Cross-Referencing the Windows API resolution routine can help identifying malware capabilities now</i>

<h4>Patching the binary and removing obfuscation</h4>
<p>The last part of this script would allow us to patch the binary to <strong>remove</strong> all of LummaC2 Windows API call obfuscation. This way we can focus only in the relevant instructions and help IDA decompiler to easily recognize the arguments that are being passed to these functions.</p>
<p>With the current implementation, we can easily see which Windows API calls are being used (as we have comments in every call that resolves a function). However, we still have many unnecessary instructions in the code, and the call is still being done with the instruction “call eax”, which does not help us much with analysis, cross-referencing, etc.</p>
<p>The goal of the following Python function for IDA is to patch the “call ResolveApiByHash” for the real Windows API call that is going to be called after resolution. From the previous script, we have a list of tuples. Each element of the tuple consists of an address (where the call resolution is made) and the Windows API call that is going to be called.</p>
<pre><code class="language-python">def patch_apicall_wrapper(patches):

    total_apis_patched = 0

    for item in patches:

        # item : (addr, apicall) (e.g (0xCAFEBABE, "kernel32_AddAtomA"))
        try:

            addr = item[0]
            apicall = item[1]

            success_patch = patch_apicall(addr, apicall)

            if success_patch:
                total_apis_patched += 1

        except Exception as err:
            print('Error patching call: {}'.format(err))

    print('Total APIs patched: {}'.format(total_apis_patched))
</code></pre>
<p>The function “<em>patch_apicall</em>” is going to be the responsible for retrieving the address of a Windows API call and patching the “<em>call ResolveApiByHash</em>” for the call to the expected export.</p>
<p>One drawback that we may find, is that we cannot resolve the address of an export from a Windows .dll that has not been loaded in the address space from the debugged process yet. To overcome this issue, we can make use of IDA “<strong>Appcall</strong>” feature. With Appcall we can execute <strong>LoadLibraryA</strong> to load any missing .dll so that we can resolve all exports just from the Entry Point. (Otherwise, we would have to wait until the malware loads the library for the first time; which would not allow us to automate everything from the Entry Point as we are doing now).</p>
<p>Once we have the address, we need to get the relative offset and then we can use <strong>0xE8</strong> opcode with this relative offset to patch the call “<em>ResolveApiByHash</em>”. Finally, we append two <strong>nop</strong> instructions after to overwrite the proceeding “<em>call eax</em>”.</p>
<pre><code class="language-python">def patch_apicall(addr, apicall):
    '''
    If it cannot be resolved, it is possible that the malware has not loaded the library yet (for example, wininet.dll). In this case, we can force the load of the module using AppCall:

    loadlib = Appcall.proto("kernel32_LoadLibraryA", "int __stdcall loadlib(const char *fn);")
    hmod = loadlib("wininet.dll")
    '''

    loadlib = Appcall.proto("kernel32_LoadLibraryA", "int __stdcall loadlib(const char *fn);")

    print('apicall: {}'.format(apicall))
    apiaddr = idc.LocByName(apicall)
    if apiaddr == 0xFFFFFFFF:
        hmod = loadlib('{}.dll'.format(apicall.split('_')[0]))
        apiaddr = idc.LocByName(apicall)
        if apiaddr == 0xFFFFFFFF:
            return False

    # At this point, apiaddr has the address of the Api call that we want. (e.g 0x772AB880 for wininet_InternetOpenA)

    rel_offset = (apiaddr - addr - 5) &amp; 0xFFFFFFFF

    idc.PatchDword(addr+1, rel_offset) # Patch “call ResolveApiByHash”  -&gt; “call InternetOpenA”

    idc.PatchByte(addr+5, 0x90) # patch with nop
    idc.PatchByte(addr+6, 0x90) # patch with nop

    return

setup("c:\murmurhash2_hashes_dict.json") # Big Python dict. With hashes and export names
patches = resolve_all_APIs(0x004082D3) # Use address of “ResolveApiByHash”
patch_apicall_wrapper(patches) # Patch the binary
</code></pre>
<p>With this implementation, we have now <strong>patched</strong> all the calls to the real exports that the malware wants to use. However, IDA has now trouble identifying and displaying properly the arguments of the functions in the decompiler view. An example of this can be seen in the following picture:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/95afc963-a084-4f70-98c7-ca281647c09a.png" alt="Malware Internals - Figure 10. Example before (left) and after (right) the script execution. Binary is patched to call the real function" />

<i>Figure 10. Example before (left) and after (right) the script execution. Binary is patched to call the real function</i>

<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/f45df5e5-16c8-4fbc-b91c-4fa1126a8229.png" alt="Malware Internals - Figure 11. Example of how arguments cannot be easily recognized at first by IDA decompiler yet" />

<i>Figure 11. Example of how arguments cannot be easily recognized at first by IDA decompiler yet</i>

<p>The last thing to do here is to remove the instructions related with the Windows API call resolution (registry operations used to move the .dll string and the hash). This way the decompiler will show the arguments and everything as expected, while the code will look clean and show only the necessary instructions. This would be the final implementation of the previous “<em>patch_apicall</em>” function:</p>
<pre><code class="language-python">def patch_apicall(addr, apicall):
    '''
    If it cannot be resolved, it is possible that the malware has not loaded the library yet (for example, wininet.dll). In this case, we can force the load of the module using AppCall:

    loadlib = Appcall.proto("kernel32_LoadLibraryA", "int __stdcall loadlib(const char *fn);")
    hmod = loadlib("wininet.dll")
    '''

    loadlib = Appcall.proto("kernel32_LoadLibraryA", "int __stdcall loadlib(const char *fn);")

    print('apicall: {}'.format(apicall))
    apiaddr = idc.LocByName(apicall)
    if apiaddr == 0xFFFFFFFF:
        hmod = loadlib('{}.dll'.format(apicall.split('_')[0]))
        apiaddr = idc.LocByName(apicall)
        if apiaddr == 0xFFFFFFFF:
            return False

    # At this point, apiaddr has the address of the Api call that we want. (e.g 0x772AB880 for wininet_InternetOpenA)

    rel_offset = (apiaddr - addr - 5) &amp; 0xFFFFFFFF

    idc.PatchDword(addr+1, rel_offset) # Patch “call ResolveApiByHash”  -&gt; “call InternetOpenA”

    idc.PatchByte(addr+5, 0x90) # patch with nop
    idc.PatchByte(addr+6, 0x90) # patch with nop
 
    curr_ea = addr

    for _ in range(20): # Search maximum the last 20 instructions before the call to ResolveApiByHash

        prev_instruction_ea = idc.PrevHead(curr_ea)
        instruction = idc.GetDisasm(prev_instruction_ea)

        instruction_cut = instruction.replace(' ', '')
        if 'movecx' in instruction_cut or \
            'movedx' in instruction_cut:

            operand_type = idc.GetOpType(prev_instruction_ea, 1)
            param = idc.GetOperandValue(prev_instruction_ea, 1)

            # Only allow for:
            # 1- General Register
            # 2- Direct Memory reference (DATA)
            # 5- Immediate Value

            if operand_type not in [1, 2, 5]:
                curr_ea = prev_instruction_ea
                continue

            if param &lt; 0x10:

                if not 'eax' in instruction_cut: # Avoid patching result from previous calls

                    # This means the operand is a register (1-byte mov operation e.g 8B CE  mov     ecx, esi)
                    # NOP the instruction
                    PatchNops(prev_instruction_ea, 2)


            else:
                # This means the operand is 4-byte length (e.g B9 D6 3F B0 78  mov     ecx, 78B03FD6h)
                PatchNops(prev_instruction_ea, 5)

        curr_ea = prev_instruction_ea


    return True

def PatchNops(addr, size):
    for i in range(size):

        print("Patching NOP at addr: {}".format(hex(addr+i)))

        idc.PatchByte(addr+i, 0x90) #patch with nop


setup("c:\murmurhash2_hashes_dict.json") # Big Python dict. With hashes and export names
patches = resolve_all_APIs(0x004082D3) # Use address of “ResolveApiByHash”
patch_apicall_wrapper(patches) # Patch the binary
</code></pre>
<p>With this in place, we can execute the script in a debugging session on Entry Point and remove <strong>most</strong> of the code related to Windows API call obfuscation while patching the binary to get an <strong>equivalent</strong> working sample easier to analyze and reverse engineer.</p>
<p>The following figure shows the result of patching the binary with our script:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/b1e8d2c8-a2a6-4a6c-bf3e-b38549a1312b.png" alt="Malware Internals - Figure 12. Example of how instructions related to Windows API call obfuscation have been patched with nops" />

<i>Figure 12. Example of how instructions related to Windows API call obfuscation have been patched with nops</i>

<p>As we can see, we only have relevant instructions in our disassembly (nop instructions have patched out Windows API call obfuscation scheme). And, as a result, it is easy for IDA decompiler now to understand and properly display the arguments for our Windows API calls in the first attempt:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/e5f26156-fbd8-4987-87a5-dbf769ba8a72.png" alt="Malware Internals - Figure 13. Example before (up) and after (down) patching the binary with our last script." />

<i>Figure 13. Example before (up) and after (down) patching the binary with our last script.</i>

<p>Here we have the main networking exfiltration routine <strong>before</strong> applying any of our scripts and <strong>after</strong> patching the binary. As we can see, the result is a much clear and easy to understand routine:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/e9f4dc08-60b1-489a-b962-725b0b756e6c.png" alt="Malware Internals - Figure 14. Decompiled view of Network Exfiltration routine without patching the binary" />

<i>Figure 14. Decompiled view of Network Exfiltration routine without patching the binary</i>

<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/404585ec-0966-43b3-ad5d-f1ec3c9ae783.png" alt="Malware Internals - Figure 15. Decompiled view of Network Exfiltration routine after patching the binary with our script" />

<i>Figure 15. Decompiled view of Network Exfiltration routine after patching the binary with our script</i>

<h3>Strings Obfuscation</h3>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/78960a5a-2828-42ff-ac58-a8c0cb1de6fe.png" alt="Malware Internals - Figure 16. View of LummaC2 obfuscated strings in the binary" />

<i>Figure 16. View of LummaC2 obfuscated strings in the binary</i>

<p>LummaC2 “obfuscates” most of the strings used in the malware in order to evade detection. By stripping every occurrence of “<strong>edx765</strong>” from a given string, we can easily get the original one. Most of these strings are used to walk through sensitive files inside directories.</p>
<p>As it can be seen, the obfuscation method is very simple and for this reason, it is probable that we see changes in this implementation in future versions of the malware.</p>
<h4>LummaC2 Workflow</h4>
<p>The following diagram shows <strong>LummaC2</strong> main <strong>workflow</strong>. This malware goes straight to the point and only cares about exfiltrating stolen information. No persistence mechanisms are used and there is no control on how many malware instances can run at the same time. One difference regarding many information stealers is that this malware family does not care about the machine being infected, while others avoid infecting machines coming from the Commonwealth of Independent States.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/9cc6af00-8111-494e-8f5c-cd9aeda0b89d.png" alt="Malware Internals - Figure 17. LummaC2 main workflow diagram" />

<i>Figure 17. LummaC2 main workflow diagram</i>

<h4>Information Gathering</h4>
<p>LummaC2 gathers information from the victim system. This information is saved in a file named “<strong>System.txt</strong>” prior to <strong>zip compression</strong> and exfiltration. The information gathered from the infected machine includes the Username, Hardware ID, Screen Resolution and more:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/35984ff1-18f7-4fa5-829d-a8756744c870.png" alt="Malware Internals - Figure 18. System Information gathered from LummaC2" />

<i>Figure 18. System Information gathered from LummaC2</i>

<p>This information is obtained using the following Windows API calls respectively:</p>
<ul>
<li><p>GetComputerNameA</p>
</li>
<li><p>GetUserNameA</p>
</li>
<li><p>GetCurrentHwProfileA</p>
</li>
<li><p>GetSystemMetrics</p>
</li>
<li><p>GetSystemDefaultLocaleName</p>
</li>
<li><p>cpuid</p>
</li>
<li><p>GetPhysicallyInstalledSystemMemory</p>
</li>
</ul>
<h4>Steal Important Files</h4>
<p>LummaC2 will also steal files from the victim machine and save them under “Important Files/Profile”. What happens to be considered here an “important file” is actually every *<strong>.txt</strong> file under %userprofile%. This is done in a <strong>recursive</strong> call that traverses %userprofile% with a maximum recursion depth of <strong>2</strong> directories.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/435864ac-eab2-4c5b-8bba-97a4d499cdf6.png" alt="Malware Internals - Figure 19. LummaC2 code responsible for gathering system information and important files" />

<i>Figure 19. LummaC2 code responsible for gathering system information and important files</i>

<h4>Targeted Software</h4>
<p>After gathering information from the infected machine and stealing important files, it proceeds to steal crypto wallets for Binance, Electrum and Ethereum (in this order). Once this is finished it exfiltrates data (<strong>ZIP compressed</strong>) to the C2 and continues the stealing process.</p>
<table style="min-width:25px"><colgroup><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Crypto Wallets</strong></p></td></tr><tr><td><p>Binance</p></td></tr><tr><td><p>Electrum</p></td></tr><tr><td><p>Ethereum</p></td></tr></tbody></table>

<i>Table 1. LummaC2 targeted Crypto Wallets</i>

<p>After the first exfiltration to the Command and Control server, LummaC2 proceeds to steal relevant Browsers information like <strong>Login Data</strong>, <strong>History</strong>, <strong>cookies</strong>, etc. Affected Browsers are:</p>
<table style="min-width:25px"><colgroup><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Web Browsers</strong></p></td></tr><tr><td><p>Chrome</p></td></tr><tr><td><p>Chromium</p></td></tr><tr><td><p>Edge</p></td></tr><tr><td><p>Kometa</p></td></tr><tr><td><p>Vivaldi</p></td></tr><tr><td><p>Brave-Browser</p></td></tr><tr><td><p>Opera Stable</p></td></tr><tr><td><p>Opera GX Stable</p></td></tr><tr><td><p>Opera Neon</p></td></tr><tr><td><p>Mozilla Firefox</p></td></tr></tbody></table>

<i>Table 2. LummaC2 targeted Web Browsers</i>

<p>The malware also targets Crypto Wallets and <strong>two-factor authentication</strong> (2FA) browser <strong>extensions</strong> that may have been installed in the system. The following figure shows <strong>LummaC2</strong> searching for these elements:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/53110e29-43bc-4635-946a-ff085a76a394.png" alt="Malware Internals - Figure 20. LummaC2 targeting Crypto Wallets and two-factor authentication (2FA) extensions" />

<i>Figure 20. LummaC2 targeting Crypto Wallets and two-factor authentication (2FA) extensions</i>

<table style="min-width:100px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Crypto Wallet Extensions</strong></p></td></tr><tr><td><p>Metamask</p></td><td><p>BitApp</p></td><td><p>Sollet</p></td><td><p>Nash Extension</p></td></tr><tr><td><p>TronLink</p></td><td><p>iWlt</p></td><td><p>Auro</p></td><td><p>Hycon Lite Client</p></td></tr><tr><td><p>Ronnin Wallet</p></td><td><p>Wombat</p></td><td><p>Polymesh</p></td><td><p>ZilPay</p></td></tr><tr><td><p>Binance Chain Wallet</p></td><td><p>MEW CX</p></td><td><p>ICONex</p></td><td><p>Coin98</p></td></tr><tr><td><p>Yoroi</p></td><td><p>Guild</p></td><td><p>Nabox</p></td><td><p>Cyano</p></td></tr><tr><td><p>Nifty</p></td><td><p>Saturn</p></td><td><p>KHC</p></td><td><p>Byone</p></td></tr><tr><td><p>Math</p></td><td><p>NeoLine</p></td><td><p>Temple</p></td><td><p>OneKey</p></td></tr><tr><td><p>Coinbase</p></td><td><p>Clover</p></td><td><p>TezBox</p></td><td><p>Leaf</p></td></tr><tr><td><p>Guarda</p></td><td><p>Liquality</p></td><td><p>DAppPlay</p></td><td><p> </p></td></tr><tr><td><p>EQUAL</p></td><td><p>Terra Station</p></td><td><p>BitClip</p></td><td><p> </p></td></tr><tr><td><p>Jaxx Liberty</p></td><td><p>Keplr</p></td><td><p>Steem Keychain</p></td><td><p> </p></td></tr></tbody></table>

<i>Table 3. LummaC2 targeted Crypto Wallet extensions</i>

<table style="min-width:25px"><colgroup><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Two-Factor Authentication (2FA) Extensions</strong></p></td></tr><tr><td><p>Authenticator</p></td></tr><tr><td><p>Authy</p></td></tr><tr><td><p>EOS Authenticator</p></td></tr><tr><td><p>GAuth Authenticator</p></td></tr><tr><td><p>Trezor Password Manager</p></td></tr></tbody></table>

<i>Table 4. LummaC2 targeted two-factor authentication (2FA) extensions</i>

<h4>Network Data Exfiltration</h4>
<p>Communication with the Command and Control server is one-way only. The malware does not expect any response from its C2. As we can see from <strong>LummaC2</strong> workflow diagram, the malware contacts the C2 in different stealing phases. After each phase, stolen information is sent to the C2 <strong>ZIP compressed</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/8037b239-37f8-4db7-846b-5d6ee22a7d0b.png" alt="Malware Internals - Figure 21. LummaC2 HTTP POST request headers" />

<i>Figure 21. LummaC2 HTTP POST request headers</i>

<p>LummaC2 exfiltrates stolen information via HTTP <strong>POST</strong> request. These requests are made to the resource “/c2sock” and use <strong>multipart/form-data</strong> (combines one or more sets of data into a single body, separated by boundaries) to upload a compressed <strong>ZIP</strong> file containing the stolen information along with more information (like the hardware id, obtained previously with <strong>GetCurrentHwProfileA</strong>). The following figure shows the different set of data sent to the C2:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/ba33983b-8ae6-4420-b73e-f7111f1d5d84.png" alt="Malware Internals - Figure 22. LummaC2 multipart/form-data fields used when exfiltrating information" />

<i>Figure 22. LummaC2 multipart/form-data fields used when exfiltrating information</i>

<p>First field (highlighted one) is the filed with name “<strong>file</strong>”. This includes the <strong>ZIP compressed</strong> file with <strong>sensitive information</strong> being exfiltrated.</p>
<p>The next field with name “<strong>hwid</strong>” contains the Hardware Id previously retrieved using <strong>GetCurrentHwProfileA</strong></p>
<p>The field with name “<strong>pid</strong>” can be understood as “petition Id”. It is the number of the exfiltration attempt. As we saw earlier in the workflow diagram, LummaC2 can exfiltrate up to 3 different times after each stealing phase. From the analyzed sample, we can only expect to see values 1, 2 or 3, which means that if we detect LummaC2 networking activity with different “petition Id” we will be dealing with a newer/updated version of the malware.</p>
<p>Finally, the last field with name “<strong>lid</strong>” comes with the hardcoded value “xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”. This is known as the “<strong>Lumma ID</strong>” and it can be understood as an identifier of the malware that may refer to the build or campaign id. This field is also used when gathering system information (in the creation of “System.txt” file being exfiltrated).</p>
<p>Network data being transmitted to the <strong>C2</strong> can be recognized in the following figure, where an excerpt of the first <strong>HTTP POST</strong> exfiltration request (request with “pid” equals 1) using multipart/form-data is sending a ZIP compressed file:</p>
<img src="https://cdn.hashnode.com/uploads/covers/697cff2ca8e57526a557afde/da85c71e-4031-4ef5-b45f-1cb6f7d9ada7.png" alt="Malware Internals - Figure 23. LummaC2 Hex view of HTTP POST request for exfiltration" />

<i>Figure 23. LummaC2 Hex view of HTTP POST request for exfiltration</i>

<h3>MITRE ATT&amp;CK</h3>
<table style="min-width:75px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Tactic</strong></p></td><td><p><strong>Technique ID</strong></p></td><td><p><strong>Technique</strong></p></td></tr><tr><td><p>Defense Evasion</p></td><td><p>T1140</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1140/" style="pointer-events:none"><u>Deobfuscate/Decode Files or Information</u></a></p></td></tr><tr><td><p>Defense Evasion</p></td><td><p>T1027</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1027/" style="pointer-events:none"><u>Obfuscated Files or Information</u></a></p></td></tr><tr><td><p>Credential Access</p></td><td><p>T1539</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1539/" style="pointer-events:none"><u>Steal Web Session Cookie</u></a></p></td></tr><tr><td><p>Credential Access</p></td><td><p>T1555</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1555/" style="pointer-events:none"><u>Credentials from Password Stores</u></a></p></td></tr><tr><td><p>Credential Access</p></td><td><p>T1552</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1552/" style="pointer-events:none"><u>Unsecured Credentials</u></a></p></td></tr><tr><td><p>Discovery</p></td><td><p>T1083</p></td><td><p><a target="_self" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="http://web.archive.org/web/20230601192423/https://attack.mitre.org/techniques/T1083/" style="pointer-events:none"><u>File and Directory Discovery</u></a></p></td></tr><tr><td><p>Discovery</p></td><td><p>T1082</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1082/" style="pointer-events:none"><u>System Information Discovery</u></a></p></td></tr><tr><td><p>Discovery</p></td><td><p>T1033</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1033/" style="pointer-events:none"><u>System Owner/User Discovery</u></a></p></td></tr><tr><td><p>Collection</p></td><td><p>T1560</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1560/" style="pointer-events:none"><u>Archive Collected Data</u></a></p></td></tr><tr><td><p>Collection</p></td><td><p>T1119</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1119/" style="pointer-events:none"><u>Automated Collection</u></a></p></td></tr><tr><td><p>Collection</p></td><td><p>T1005</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1005/" style="pointer-events:none"><u>Data from Local System</u></a></p></td></tr><tr><td><p>Exfiltration</p></td><td><p>T1041</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1041/" style="pointer-events:none"><u>Exfiltration over C2 Channel</u></a></p></td></tr><tr><td><p>Exfiltration</p></td><td><p>T1020</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1020/" style="pointer-events:none"><u>Automated Exfiltration</u></a></p></td></tr><tr><td><p>Command and Control</p></td><td><p>T1071</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1071/" style="pointer-events:none"><u>Application Layer Protocol</u></a></p></td></tr><tr><td><p>Command and Control</p></td><td><p>T1132</p></td><td><p><a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="https://attack.mitre.org/techniques/T1132/" style="pointer-events:none"><u>Data Encoding</u></a></p></td></tr></tbody></table>

<i>Table 5. LummaC2 MITRE ATT&amp;CK matrix</i>

<h3>Conclusion</h3>
<p>As it has been shown, LummaC2 behaves similar to other information stealers. By capturing sensitive data from infected machines, including business credentials, it can do a lot of damage. For example, compromised credentials can be used to achieve privilege escalation and lateral movement. Compromised business accounts can also be used to and further distribute the malware.</p>
<p>The fact the malware is being actively used in the wild indicates the professionalization in the development of these products. Bad actors are willing to pay for these tools because they prefer quality, and more features. In return, they expect to see more profit from the exfiltrated data.</p>
<h3>IOCs</h3>
<p><strong>Hash</strong></p>
<p>LummaC2 sample:</p>
<ul>
<li>277d7f450268aeb4e7fe942f70a9df63aa429d703e9400370f0621a438e918bf</li>
</ul>
<p><strong>C2</strong></p>
<p>LummaC2 Command and Control server:</p>
<ul>
<li>195[.]123[.]226[.]91</li>
</ul>
<h3>References</h3>
<p>“LummaC2 Stealer: A Potent Threat to Crypto Users”, January, 2023. [Online]. Available: <a href="https://blog.cyble.com/2023/01/06/lummac2-stealer-a-potent-threat-to-crypto-users/">https://blog.cyble.com/2023/01/06/lummac2-stealer-a-potent-threat-to-crypto-users/</a> [Accessed March 20, 2023]</p>
<p>“Popularity spikes for information stealer malware on the dark web”, December, 2022. [Online]. Available: <a href="https://www.accenture.com/us-en/blogs/security/information-stealer-malware-on-dark-web">https://www.accenture.com/us-en/blogs/security/information-stealer-malware-on-dark-web</a> [Accessed March 20, 2023]</p>
]]></content:encoded></item></channel></rss>