A signed PDF viewer that quietly takes orders: the QuartzLine “zenith” backdoor

About this post: This analysis and write-up were produced by Claude (Opus 5.5), an AI model by Anthropic, working as an agent in Claude Code on a REMnux analysis VM. Tools used: Malcat (including KesaKode), capstone, Python, capa, FLOSS, StringSifter, speakeasy, a locally sandboxed Wine run, the ANY.RUN sandbox and Hatching Triage. An independent reviewer agent checked the technical report and this post against the raw evidence.

TL;DR

  • paperline.exe, also seen as QuartzLine_1.x.exe, is a 7.9 MB Windows program written in Rust. It opens a real-looking PDF viewer called QuartzLine, with a working PDF parser built in.
  • It is EV-signed by Guangzhou Guoying Communication Technology Co., Ltd. Sectigo has since revoked that certificate.
  • Hidden inside is a second module its authors called "zenith". It gives the machine a permanent ID, checks in with a command-and-control (C2) server, the attacker’s server that hands out instructions, and waits for orders.
  • The server can tell it to run any Windows command in a hidden window and send back the output. It can also tell it to tamper with a Chrome-family browser profile.
  • We cracked the traffic encryption and read every conversation we could decrypt (four of seven sandbox sessions). In each one the server said "nothing to do", so we never saw a real command.
  • Public sandboxes scored it as low risk or clean. Those verdicts are wrong.

But it’s signed!

Code signing is a digital seal on a program. It says "this file came from this identity and hasn’t changed since". An EV (Extended Validation) certificate is the premium kind: the company’s identity is checked more thoroughly before it’s issued, which makes it look more trustworthy to users and some security tools.

This certificate was issued by Sectigo to a Guangzhou telecom-technology company and is valid from April 2026 to April 2027. But the program inside claims to come from "Summit Works", calls itself "Paperline Document Viewer" in its file details, and shows "QuartzLine" on screen. Three names, none of which match the signer. The team behind this blog has reported over 2,500 signed malware samples, and a signer that has nothing to do with the product it signs is one of the most common patterns we see.

The certificate has been reported to the CA and added to the CertGraveyard, a public database of certificates abused to sign malware. As of 9 October 2026, Sectigo’s revocation list shows it as revoked with the reason "Privilege Withdrawn". The revocation is backdated to the day the certificate was issued, so every signature it ever made is now invalid.

The part you see: a viewer that looks real

Run it and you get a clean, modern document viewer window with Open, Search, page thumbnails and zoom, and the program has a real PDF parser built in. It is built with Slint, a Rust GUI toolkit. Nothing about the window hints at anything else going on.

That is the point. A lure that actually does its job doesn’t get uninstalled, and nobody goes looking for why it’s still running.

What happens when QuartzLine starts: the viewer opens while the hidden "zenith" module checks in and waits for orders.
What happens when QuartzLine starts: the viewer opens while the hidden “zenith” module checks in and waits for orders.

The part you don’t see: "zenith"

Alongside the viewer code, the file contains source paths for a second, unrelated component: zenith\src\config.rs and a set of short paths like C:\r\src\e\commands.rs and C:\r\src\d\c\profile.rs. Compiled programs often keep the paths of their source files for error messages, and short, cryptic folder names limit what those leaked paths reveal. The file names still give away what each part does.

When the viewer starts, zenith does three things:

  1. Gives the machine an ID. It writes %LOCALAPPDATA%\QuartzLine\terrain.dat. The file looks like a settings file (antialiasing=, buffer_size=, and so on), but its last line is zenith= followed by a random ID. That ID identifies this machine to the server from then on.
  2. Checks in. It sends a message to /quarry/vein on one of three domains: westbeavers[.]com, motherotters[.]com or healskills[.]com, all behind Cloudflare, which hides the real server behind Cloudflare’s own addresses. The message includes the version of the hidden module (the implant, 6.1.3), a campaign ID (QNPVXZGJYUSLTREHK), a tag (QZRT07), the program’s path and the machine ID.
  3. Reports its status to /quarry/deposit, using short status codes from a built-in table.

The whole protocol has a geology theme: quarry, vein, deposit, stratum, bedrock, and JSON field names like gneiss, felsite and obsidian. That’s just naming, but it makes the traffic easy to recognise once you know it.

What the server can make it do

From the code (not from live traffic, see below), the server’s reply can trigger two kinds of work.

Run commands. zenith can run any command through cmd /C with no window, then report the exit code, how long it took, and the output. Anything you can do at a command prompt, the operator can do remotely. Installing more malware, adding persistence and looking around the network are all possible, even though none of those is built into the file itself.

Tamper with a browser profile. A separate handler works with a browser profile folder laid out the way Chromium-based browsers (Chrome, Edge and similar) lay theirs out. The program contains no browser names at all: the server tells it which browser, which profile and which files. When asked, it reads profile files and sends them to the server, writes files the server sends into the profile, force-closes the browser and starts it again with that profile.

Browser profiles hold your settings, extensions and logged-in sessions, and the browser protects some of those settings against outside changes. Code that uploads profile files, swaps in server-supplied versions and then restarts the browser looks like a way to change browser settings, for example adding an extension, without the user’s involvement. We want to be clear about the limits: the server never sent this task in any run we saw, so we can’t say exactly which files were targeted or what was written.

Cracking the "quarry" traffic

The messages travel over HTTPS, but they are also scrambled inside the HTTPS layer, so even a network tool that decrypts HTTPS sees only a base64 blob. Reversing the code showed four steps:

Each message is compressed, XOR-chained with a random seed, bit-rotated and shuffled before being base64-encoded.
Each message is compressed, XOR-chained with a random seed, bit-rotated and shuffled before being base64-encoded.
  1. gzip compresses the JSON.
  2. A chained XOR mixes each byte with the previous one, a fixed key and a random seed chosen for each message.
  3. Each byte’s bits are rotated by an amount that depends on the seed.
  4. The bytes are shuffled in an order generated from the seed (a seeded Fisher–Yates shuffle, like shuffling a deck in a repeatable way).

The seed travels at the front of the message, so anyone who knows the recipe can undo it. It’s obfuscation, not real cryptography. We wrote a decryptor and tested it on traffic from Triage (September and October), an ANY.RUN run we submitted, and messages the sample generated in our own sandbox. Every message decrypted to valid JSON.

What the servers actually said

We now have decrypted conversations from four machines: one Triage virtual machine from September, one ANY.RUN run, and two new Triage machines (Windows 10 and Windows 11) that ran it for ten minutes on 9 October.

All four look the same. Three more Triage machines from September also reached the server, but their traffic couldn’t be decrypted. A few seconds after launch the program sends one check-in and one status report. The server replies with the equivalent of "no update, all good":

{"lignite":{"update":false},"schist":{"success":true}}

Then nothing. In the two ten-minute runs, zenith didn’t contact the server again for the rest of the recording (about eight to nine and a half minutes of captured traffic). Many backdoors ask for work every minute; this one doesn’t. We couldn’t find a timer in the code, but Rust’s async runtime handles timers internally, so a much longer interval isn’t ruled out. Either way, the network footprint is tiny.

Every reply we could decrypt was the same "no work" answer, and the replies we couldn’t decrypt are the same size. That could mean the operators simply weren’t handing out tasks, or that their server filters out machines it doesn’t like. From the outside we can’t tell which.

Why the sandboxes missed it

ANY.RUN scored it 30/100 with "No threats detected". Earlier Triage runs scored it 1 to 3 out of 10. The new Triage run scored 8/10, but that came from the revoked certificate and from Windows background activity (an explorer.exe restart about seven minutes in) that showed no link to this file. The sample itself only earned a minor flag for reading the computer’s host name. None of the scores reflects what zenith is built to do. That’s not surprising:

  • The visible program is a genuine viewer, so its behaviour looks normal.
  • The backdoor only talks to its server, and the server didn’t send any tasks during the analysis window.
  • The traffic is HTTPS with a second layer of scrambling inside.
  • One-character edits to text inside the binary, both standard Rust library paths and the authors’ own (for example mod.rs changed to mod.rn, main.rs to main.rj), may be meant to throw off detection rules or similarity matching that rely on those strings.

A low sandbox score means "nothing bad happened while we watched". It does not mean "this can’t do anything bad".

What we couldn’t see

  • No live tasking. In every conversation we could decrypt, the server replied that there was nothing to do. Everything above about commands and the browser profile comes from reading the code.
  • The exact browser files and changes are unknown. The server supplies them, and it never did so while we watched.
  • The update path is unexplored. zenith has --update-check and --update-view modes and extra server paths (/quarry/stratum, /quarry/gravel, /quarry/bedrock, /quarry/sandstone, /quarry/seam) that we never saw used.
  • We didn’t contact the servers ourselves. We only read traffic that sandboxes had already captured.
  • No family name. KesaKode, Malcat’s service that matches code against known malware, attributed 54% of its malware-labelled matches to GeckoStealer. Most of those matches are shared Rust library code rather than the backdoor’s own logic, and a few are unexplained. We treat it as an unnamed backdoor.

Advice

For everyday users: download PDF readers and similar tools only from well-known vendors. A signature tells you who paid for the certificate, not whether the program is honest. If an app’s file details, signer and window title all name different companies, treat that as a red flag.

For defenders:

  • Look for %LOCALAPPDATA%\QuartzLine\terrain.dat, and for zenith= in any small settings-style file.
  • Alert on POST requests to /quarry/vein or /quarry/deposit. The implant sends no User-Agent header (the field where software normally names itself), which is unusual for desktop software.
  • Watch for a non-browser process that runs taskkill /F against a browser and then restarts it with --user-data-dir.
  • Watch for cmd.exe /C launched by a "document viewer".
  • Block the domains, certificate serial and hash below.

IOCs

Indicators of compromise (IOCs) are the hashes, domains and other traces you can search for in your own logs.

Type Value
SHA256 7f1db334fd3302cf98dd4afddf6f90e98e2a496fe7e86bf1832b89d02f3d73c3
MD5 d62d4b7811e37ce789b40ff9343e3458
File names paperline.exe, QuartzLine_1.3177.99.exe, QuartzLine_1.9758.76.exe, QuartzLine_1.9740.56.exe
C2 domains westbeavers[.]com, motherotters[.]com, healskills[.]com (Cloudflare)
URLs (seen) hxxps://<c2>/quarry/vein, hxxps://<c2>/quarry/deposit
URLs (in config, not seen) /quarry/stratum, /quarry/gravel, /quarry/bedrock, /quarry/sandstone, /quarry/seam
Host artefact %LOCALAPPDATA%\QuartzLine\terrain.dat (last line zenith=<uuid>)
Campaign IDs build QNPVXZGJYUSLTREHK, tag QZRT07, implant version 6.1.3
Certificate Guangzhou Guoying Communication Technology Co., Ltd., Sectigo Public Code Signing CA EV R36, serial 1b56cb4e99fb9444645a1ce7ffc0a46b (revoked, "Privilege Withdrawn")
Sandboxes ANY.RUN task c306e2d9-4623-432a-9069-6ab3a93c0c6a; Triage 260910-t3f33sej3z, 260916-xs49psyz7m, 260917-cbhn6axemh, 261009-pq4ldsex2m

Wrapping up

QuartzLine is a good example of a lure that does its job too well: a polished, working app, a premium signature and a backdoor that stays quiet until someone tells it otherwise. The interesting part isn’t anything zenith did in a sandbox. It’s what it’s built to do once its operator decides your machine is worth the attention.

Thanks for reading, and stay safe.