Back to Research

Lumma Stealer Research Analysis

This report is an analysis of the information mentioned in this blog post

I will start with Execution & Evasion Techniques: I have picked the methods that I found interesting the most. Starting with obfuscation it uses Low-Level Virtual Machine (LLVM), trigonometry to track mouse movements.

1. Low Level Virtual Machine (LLVM)

LLVM is an infrastructure that was created to optimize code at compile time. Its main use is to make different programming languages allowing them to share tools before they are converted to machine code.

This process can be done in three steps, the compiler has three parts:

  1. Frontend: Parses the source code and converts it into Intermediate Representation (IR) - a form of syntax that is closest to hardware.
  2. Middle end: Analyzes this IR and optimizes it.
  3. Backend: Converts the IR into machine code.
LLVM Compiler Architecture

LLVM Compiler Architecture

LLVM makes writing new programming languages easier.

Malware Abuse of LLVM

Malware authors can abuse this functionality by:

  • Reconstructing the program control flow
  • Making an If-else statement look like a loop or switch statement
  • Reconstructing instructions into mathematical equivalent forms
  • Injecting useless code or dead code that does not affect or add anything to the program but makes it complex

2. Trigonometry to Track Mouse Movements

In the blog, it's mentioned that Lumma Stealer uses this technique for sandbox evasion. It utilizes trigonometry to track mouse movements to escape sandbox. It would not execute the payload unless human activity was detected.

I have done my own research to figure this method and test it myself.

Windows API Functions Used

A. Mouse Tracking Functions

Function Library Purpose
GetCursorPos() user32.dll Retrieves the current mouse cursor position (x, y)
GetSystemMetrics(SM_MOUSEPRESENT) user32.dll Checks if a physical mouse is connected (anti-VM check)
GetAsyncKeyState(VK_ESCAPE) user32.dll Detects if the ESC key is pressed (for graceful exit)

B. Timing Functions

Function Library Purpose
Sleep() kernel32.dll Pauses execution to control sampling rate
std::chrono (C++) Standard Library High-precision timing for movement analysis

The Mathematical Logic Behind It

A. Calculating Movement Angle

The program computes the angle between two consecutive mouse positions using atan2 (arctangent of dy/dx):

double calculateMovementAngle(POINT from, POINT to) {
    double dx = to.x - from.x;
    double dy = to.y - from.y;
    return radiansToDegrees(atan2(dy, dx)); // converts to degrees (0-360)
}
  • dx and dy - Differences in x and y coordinates
  • atan2(dy, dx) - Computes the angle in radians
  • radiansToDegrees() - Converts to degrees for easier interpretation

B. Normalizing Angle Differences

Since angles wrap around (0 = 360), we normalize them to ensure correct comparisons:

double normalizeAngle(double angle) {
    angle = fmod(angle, 360.0); // ensures angle is within 0-360
    if (angle < 0) angle += 360.0;
    return angle;
}

C. Determining Human vs. Bot Movement

The program checks if angle changes exceed a threshold (45 by default):

double angleDifference(double a, double b) {
    double diff = fabs(normalizeAngle(a) - normalizeAngle(b));
    return fmin(diff, 360.0 - diff); // get smallest difference (accounts for wrap-around)
}
  • Human movements: Large angle variations (>45°)
  • Bot/sandbox movements: Small angle variations (straight lines, repetitive patterns)

The Program Flow

  1. It checks the mouse presence using GetSystemMetrics(SM_MOUSEPRESENT) to see whether the mouse is connected
  2. Then sample mouse positions - Every 100ms, it records the cursor position using GetCursorPos()
  3. Store and Analyze Angles - Maintains a sliding window of the last 10 angles
  4. Determine Human vs. Bot: average angle change > 45 Human detected. If the average angle change ≤ 45 Bot

Code Sample

#define _USE_MATH_DEFINES
#include <Windows.h>
#include <cmath>
#include <iostream>
#include <vector>
#include <chrono>

// Constants
constexpr int SAMPLE_INTERVAL_MS = 100;  // Time between mouse position samples
constexpr double ANGLE_THRESHOLD = 45.0; // Degrees difference to consider human-like
constexpr int MIN_SAMPLES = 5;           // Minimum samples before making determination
constexpr int DETECTION_WINDOW = 10;     // Number of recent samples to consider

// Converts radians to degrees
inline double radiansToDegrees(double radians) {
    return radians * (180.0 / M_PI);
}

double calculateMovementAngle(POINT from, POINT to) {
    double dx = to.x - from.x;
    double dy = to.y - from.y;
    return radiansToDegrees(atan2(dy, dx));
}

double normalizeAngle(double angle) {
    angle = fmod(angle, 360.0);
    if (angle < 0) angle += 360.0;
    return angle;
}

double angleDifference(double a, double b) {
    double diff = fabs(normalizeAngle(a) - normalizeAngle(b));
    return fmin(diff, 360.0 - diff);
}

int main() {
    std::vector<double> angleHistory;
    POINT prevPos, currentPos;
    bool mousePresent = GetSystemMetrics(SM_MOUSEPRESENT);

    if (!mousePresent) {
        std::cout << "[!] No mouse detected - likely a virtual machine or sandbox\n";
        return 1;
    }

    std::cout << "Mouse movement analysis started. Move your mouse naturally...\n";
    std::cout << "Waiting for " << MIN_SAMPLES << " samples before analysis...\n";

    //get the mouse position
    GetCursorPos(&prevPos);
    auto lastSampleTime = std::chrono::steady_clock::now();

    while (true) {
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastSampleTime).count();

        if (elapsed >= SAMPLE_INTERVAL_MS) {
            GetCursorPos(¤tPos);

            // Only process if mouse has moved and keep only recent samples
            if (currentPos.x != prevPos.x || currentPos.y != prevPos.y) {
                double angle = calculateMovementAngle(prevPos, currentPos);
                angleHistory.push_back(angle);

                if (angleHistory.size() > DETECTION_WINDOW) {
                    angleHistory.erase(angleHistory.begin());
                }

                if (angleHistory.size() >= MIN_SAMPLES) {
                    double totalDiff = 0.0;
                    int comparisons = 0;

                    for (size_t i = 1; i < angleHistory.size(); i++) {
                        double diff = angleDifference(angleHistory[i], angleHistory[i-1]);
                        totalDiff += diff;
                        comparisons++;
                    }

                    double avgDiff = totalDiff / comparisons;
                    bool isHuman = avgDiff > ANGLE_THRESHOLD;

                    system("cls"); 
                    std::cout << "mouse movement analysis\n";
                    std::cout << "-----------------------\n";
                    std::cout << "samples collected: " << angleHistory.size() << "\n";
                    std::cout << "average angle change: " << avgDiff << " degrees\n";
                    std::cout << "detection threshold: " << ANGLE_THRESHOLD << " degrees\n";
                    std::cout << "conclusion: " << (isHuman ? "HUMAN" : "BOT/AUTOMATED") << "\n";
                    std::cout << "\nKeep moving mouse to update analysis...\n";
                }

                prevPos = currentPos;
            }

            lastSampleTime = now;
        }

        // Check for ESC key to exit
        if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
            break;
        }

        Sleep(10); 
    }

    return 0;
}

Compilation and Testing

I have compiled this using Visual Studio command line:

MouseAnalysis.cpp /EHsc /Fe:MouseAnalysis.exe /link user32.lib

Then run the program with the following output:

Human Detection Output

Human Detection Output

Bot Detection Output

Bot Detection Output

The program is missing a lot of mathematical and execution logic but I am demonstrating this as a PoC. An enhanced version of this can be used to detect human activity and BOT activity with high accuracy to escape sandbox.

References