Lemons, a fangless Lumma-style dropper

• Created by: Domonkos Poór

Hero image for Lemons, a fangless Lumma-style dropper

One night, surfing the pipes, I found a curious link; it had telltale signs of being something malicious (file name ended in .rar.exe) so I went ahead and downloaded it, as one does, and found what seems to be a broken infostealer / dropper.

This is the static analysis of that file. Nothing was executed and no operator infrastructure was contacted. The recovered IPNS name was deliberately left unresolved.

Classification

Facet Value
Lineage Lumma-style (archive convention only, no code overlap confirmed)
Infection phishing
Persistence Windows service, disguised as "Windows Phone" (present in the payload, unreachable in this build)
Propagation none observed
Platform windows
Runtime native, powershell
Purpose loader, infostealer (suspected)
C2 IPNS over public IPFS gateways, or a local IPFS daemon
Stage dropper
Target unknown

How it deceives

The file hides its intent behind three layers. Each layer is a normal thing doing an abnormal job.

Layer 1: the wrapper

The outer file is a real WinRAR self-extracting archive. It is a genuine PE64 module built with MSVC in June 2026, with an archive appended to the end. This is not a fake icon or a spoofed header. It is the real WinRAR SFX program.

Two details do the work:

  • The double extension .rar.exe. It reads as an archive and runs as a program.
  • The SFX config sets Setup=main.exe. When the user opens the file, the wrapper silently runs the inner file. This is documented WinRAR behavior, not a trick. See the SFX command reference.

Layer 2: the cargo

Inside the archive is one file, main.exe, protected with RAR5 AES-256 encryption. The password is 1234, and it is written in the filename itself. The lock is not a defense. It is an invitation: the victim has to open the archive to run it, and the filename tells them how.

The archive also carries a comment in Russian. Encryption plus a thin archive is basic anti-scan hygiene, because it hides the cargo from static scanners that cannot unlock the container.

Layer 3: the loader

main.exe is 5,179,200 bytes, and about 98 percent of that is zeros. It is a hollow shell. This empty space serves one real purpose: it inflates the file against scan-size heuristics.

The loader asks for Administrator rights through its manifest. It spawns processes with a hidden window. It imports only kernel32. No network API access or injection code observed.

Inside the loader: three message crates

The loader carries three embedded blobs, or crates. Each one decodes through the same pipeline:

raw bytes
  -> subtract a fixed per-crate key (0x45 or 0x4D)
  -> base64 text
  -> skip one character (alignment quirk)
  -> UTF-16 text

The result is a PowerShell payload.

Crate Content
src1 and src2 One large PowerShell script, split across two crates
src3 A second, shorter script, cut off mid-sentence
C:\ProgramData\config An 879-byte base64 blob, padded with dots, holding high-entropy bytes

The first script installs and starts Kubo, the official Go implementation of IPFS, in C:\ProgramData\kubo-system\. It then fetches live instructions. The second script talks to the local IPFS daemon on 127.0.0.1:5001, resolves an IPNS name, and downloads content by address.

The 879-byte blob is written as UTF-16 text and padded with dots that the scripts trim away. The payload is high-entropy bytes, likely the node identity or keys. The scripts refer to it as us_config.

What it was designed to do

The decoded scripts describe the intended mission.

  1. Start the engine: launch ipfs.exe daemon --routing=auto, hidden, with IPFS_PATH set.
  2. Ensure the engine exists: if Kubo is missing, download a zip from $url1 or $url2, unpack it, and copy kubo\ipfs.exe into place.
  3. Ask the boss: resolve an IPNS name. This is a mutable address that the operator can repoint at any time.
  4. Use one of two channels: public gateways (ipfs.io, dweb.link), or the local daemon through name resolve and cat.
  5. Execute the reply: iex runs the fetched snippet together with the decoded us_config blob.

Why IPFS

IPFS is the interesting choice, and it is a good one for the attacker.

  • IPFS traffic is legitimate background noise. A small node blends in.
  • IPNS is a mutable pointer. The operator changes the target live, and needs no new infection to do it.
  • Public gateways mean the first contact needs no local install at all.

The command channel is therefore content-addressed, mutable, and hosted by volunteers. There is no server to seize.

Persistence and stealth

A service-creation fragment appears in plain text inside the scripts:

... start= auto DisplayName= 'Windows Phone'

An auto-start service named after a dead Microsoft product. Nobody looks twice at "Windows Phone".

The rest of the stealth is ordinary but consistent: Administrator rights, hidden windows through CREATE_NO_WINDOW, 15-second sleeps, retry loops with cache-busting headers, and two layers of encoding across three crates to slow down signatures.

Where it breaks

This is the finding that names the case.

The loader never starts the scripts. It builds a command line from the decoded text, which is pure base64 with no spaces, and calls:

CreateProcessW(NULL, <that text>, ...)

When the program name is NULL, Windows parses the first word of the command line as the program to run. With no spaces, the entire 8 KB blob is one impossible word. The call fails. There is no powershell.exe, no cmd /c, and no interpreter prefix anywhere in the binary.

The failure is fatal, not partial:

  • The first spawn fails, so the program returns immediately.
  • The config file is never written.
  • The second script is never decoded, and the second spawn never happens.

The second spawn would fail the same way, but it is unreachable in any case. All of it sits in the success path, which I confirmed in the disassembly.

Several oddities point to an unfinished build:

  • The scripts reference variables that are never defined: $exe, $repo, $url1.
  • One script has unclosed braces.
  • One script ends mid-sentence.
  • Part of one crate is plain text instead of encoded.

Could it still be operational

Statically, this exact file has one execution path, two failing spawn calls, no injection, and no network capability. This file alone cannot execute its payloads. There are two explanations that I think could make sense:

  • A broken build of a real template. Everything except the trigger is coherent. A sibling build with a powershell -EncodedCommand prefix would fire, because the base64 texts are exactly that format.
  • One component of a larger lure. The phish may have intended to deliver other files alongside this one.

My personal opinion is that it is the result of a broken build, which is supported by some circumstantial evidence; The complete payload of a working sibling surfaced on the VirusTotal page for this sample. That sibling has the same three crates, plus the two constants this build is missing: an IPNS name and a PowerShell prefix at the two spawn sites. It also confirms the persistence wiring:

sc create 'Windows Phone' ... -EncodedCommand

The gap between the two builds is exactly two constants.

Scanners

On 2026-09-16, VirusTotal reported 35 of 58 engines as malicious, and MetaDefender reported the file as malicious.

The AV labels are generic downloader and dropper names: Wacatac, MalwareX, Paph, Yogi. No engine gave a family-specific label. VirusTotal's contacted-domain list for this sample is empty, and no files were dropped. That is consistent with a build that fires nothing.

The pass 1234 archive convention is Lumma Stealer's documented distribution pattern; this is why I opted to call it "Lumma-style".

Facts

Item Value
Sample Lemons pass 1234.rar.exe
Sample SHA256 39d0c5410d2d5de7204823ecba0a7d2fe1abbc6fa8ebec8422f7c45842c18b11
Inner file main.exe, SHA256 db7752d9de1d1b3b64377efd174d9b48df083406512061c3d8ecb2656ee29a3e
Wrapper WinRAR 7.x SFX, PE64, built 2026-06-27, archive appended at 0x75600
Cargo RAR5, AES-256, password 1234, one file
Loader PE64 GUI, admin manifest, 5,179,200 bytes, built 2026-07-30
Payloads Kubo bootstrap script, IPNS resolve script (truncated), 879-byte identity blob
C2 design IPNS mutable name through public gateways or a local daemon
Method Static analysis and public scan results. No execution. No contact with operator infrastructure.

Timeline: SFX built 2026-06-27, loader compiled 2026-07-30, archived 2026-08-27, delivered 2026-09-16. About three months from build to delivery.

Open questions

  • Monitored detonation of this exact build, to confirm that it fires nothing.
  • The exact role of the 879-byte blob, which no script in this build uses.
  • Whether the operator's IPNS name still resolves. I did not check, on purpose.

Glossary

Base64. A way to write binary data as letters and numbers. PowerShell's -EncodedCommand expects exactly this format as UTF-16, which is why the payloads look like ready-made ammunition.

IPFS. The InterPlanetary File System, a peer-to-peer file network. Kubo is its official Go implementation. A node is a small program that stores and serves content addressed by hash.

IPNS. The IPFS naming system. It is a stable name that points at changing content, and the operator can repoint it at any time. That is the command channel here.

CreateProcessW. The Windows API that starts a program. If the program name is NULL, it parses the first word of the command line as the program. No space, no word, no program, failure.

SFX. Self-extracting archive. A normal executable that unpacks an attached archive and optionally runs a file from it through the Setup= directive. Windows has shipped this concept for decades. The Wikipedia article has the history.

requireAdministrator. A manifest flag. Windows prompts for admin rights before it runs the file. The payload needs this for the service step.